simple_aws_ssm_parameter_store 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simple_aws_ssm_parameter_store/__init__.py +1 -0
- simple_aws_ssm_parameter_store/api.py +16 -0
- simple_aws_ssm_parameter_store/client.py +590 -0
- simple_aws_ssm_parameter_store/constants.py +44 -0
- simple_aws_ssm_parameter_store/model.py +210 -0
- simple_aws_ssm_parameter_store/paths.py +60 -0
- simple_aws_ssm_parameter_store/utils.py +30 -0
- simple_aws_ssm_parameter_store/vendor/__init__.py +2 -0
- simple_aws_ssm_parameter_store/vendor/pytest_cov_helper.py +148 -0
- simple_aws_ssm_parameter_store-0.3.0.dist-info/METADATA +238 -0
- simple_aws_ssm_parameter_store-0.3.0.dist-info/RECORD +15 -0
- simple_aws_ssm_parameter_store-0.3.0.dist-info/WHEEL +5 -0
- simple_aws_ssm_parameter_store-0.3.0.dist-info/licenses/AUTHORS.rst +15 -0
- simple_aws_ssm_parameter_store-0.3.0.dist-info/licenses/LICENSE.txt +21 -0
- simple_aws_ssm_parameter_store-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from .constants import ParameterType
|
|
4
|
+
from .constants import ParameterTier
|
|
5
|
+
from .constants import ResourceType
|
|
6
|
+
from .constants import DEFAULT_KMS_KEY
|
|
7
|
+
from .utils import encode_tags
|
|
8
|
+
from .utils import decode_tags
|
|
9
|
+
from .model import Parameter
|
|
10
|
+
from .client import get_parameter
|
|
11
|
+
from .client import put_parameter_if_changed
|
|
12
|
+
from .client import delete_parameter
|
|
13
|
+
from .client import get_parameter_tags
|
|
14
|
+
from .client import remove_parameter_tags
|
|
15
|
+
from .client import update_parameter_tags
|
|
16
|
+
from .client import put_parameter_tags
|
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Enhanced boto3 SSM client functions.
|
|
5
|
+
|
|
6
|
+
This module provides high-level, Pythonic wrapper functions around the AWS SSM
|
|
7
|
+
Parameter Store boto3 client operations. The functions handle error cases gracefully,
|
|
8
|
+
provide better return values, and implement common patterns like existence testing
|
|
9
|
+
and idempotent operations.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import typing as T
|
|
13
|
+
import json
|
|
14
|
+
|
|
15
|
+
import botocore.exceptions
|
|
16
|
+
from func_args.api import OPT
|
|
17
|
+
from func_args.api import remove_optional
|
|
18
|
+
|
|
19
|
+
from .constants import ParameterType
|
|
20
|
+
from .constants import ParameterTier
|
|
21
|
+
from .constants import ResourceType
|
|
22
|
+
from .utils import encode_tags
|
|
23
|
+
from .utils import decode_tags
|
|
24
|
+
from .model import Parameter
|
|
25
|
+
|
|
26
|
+
if T.TYPE_CHECKING: # pragma: no cover
|
|
27
|
+
from mypy_boto3_ssm.client import SSMClient
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_parameter(
|
|
31
|
+
ssm_client: "SSMClient",
|
|
32
|
+
name: str,
|
|
33
|
+
with_decryption: bool = False,
|
|
34
|
+
) -> Parameter | None:
|
|
35
|
+
"""
|
|
36
|
+
Get a parameter by name with built-in existence testing.
|
|
37
|
+
|
|
38
|
+
This function provides a convenient way to retrieve parameters while gracefully
|
|
39
|
+
handling non-existent parameters. Unlike the raw boto3 client which raises
|
|
40
|
+
exceptions for missing parameters, this function returns None, making it ideal
|
|
41
|
+
for existence testing and conditional parameter access.
|
|
42
|
+
|
|
43
|
+
Example usage for existence testing::
|
|
44
|
+
|
|
45
|
+
param = get_parameter(ssm_client, "/my/param")
|
|
46
|
+
if param is not None:
|
|
47
|
+
print("Parameter exists")
|
|
48
|
+
else:
|
|
49
|
+
print("Parameter does not exist")
|
|
50
|
+
|
|
51
|
+
Ref:
|
|
52
|
+
|
|
53
|
+
- `get_parameter <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.get_parameter>`_
|
|
54
|
+
|
|
55
|
+
:param ssm_client: SSM client
|
|
56
|
+
:param name: parameter name (e.g., "/app/database/host")
|
|
57
|
+
:param with_decryption: whether to decrypt SecureString parameter values
|
|
58
|
+
|
|
59
|
+
:return: ``Parameter`` object if the parameter exists, None if it does not exist.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
response = ssm_client.get_parameter(
|
|
63
|
+
Name=name,
|
|
64
|
+
**remove_optional(
|
|
65
|
+
WithDecryption=with_decryption,
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
return Parameter(_data=response["Parameter"])
|
|
69
|
+
except botocore.exceptions.ClientError as e:
|
|
70
|
+
if e.response["Error"]["Code"] in [
|
|
71
|
+
"ParameterNotFound",
|
|
72
|
+
"ParameterVersionNotFound",
|
|
73
|
+
]:
|
|
74
|
+
return None
|
|
75
|
+
raise # pragma: no cover
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _get_parameter_metadata(
|
|
79
|
+
ssm_client: "SSMClient",
|
|
80
|
+
name: str,
|
|
81
|
+
) -> dict[str, T.Any]:
|
|
82
|
+
"""
|
|
83
|
+
Get the metadata of a parameter that ``get_parameter`` does not return,
|
|
84
|
+
such as ``Description``, ``Tier``, ``KeyId``, ``AllowedPattern``, and ``Policies``.
|
|
85
|
+
|
|
86
|
+
Ref:
|
|
87
|
+
|
|
88
|
+
- `describe_parameters <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.describe_parameters>`_
|
|
89
|
+
|
|
90
|
+
:param ssm_client: SSM client
|
|
91
|
+
:param name: parameter name (e.g., "/app/database/host")
|
|
92
|
+
|
|
93
|
+
:return: The ``describe_parameters`` item of the parameter, or an empty dict
|
|
94
|
+
if the parameter does not exist.
|
|
95
|
+
"""
|
|
96
|
+
response = ssm_client.describe_parameters(
|
|
97
|
+
ParameterFilters=[
|
|
98
|
+
{
|
|
99
|
+
"Key": "Name",
|
|
100
|
+
"Option": "Equals",
|
|
101
|
+
"Values": [name],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
)
|
|
105
|
+
for item in response.get("Parameters", []):
|
|
106
|
+
if item["Name"] == name:
|
|
107
|
+
return item
|
|
108
|
+
return {}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _is_same_policies(
|
|
112
|
+
desired: str,
|
|
113
|
+
current: list[dict[str, str]] | None,
|
|
114
|
+
) -> bool:
|
|
115
|
+
"""
|
|
116
|
+
Compare the ``Policies`` JSON string passed to ``put_parameter`` with the
|
|
117
|
+
``Policies`` list returned by ``describe_parameters``, ignoring key order
|
|
118
|
+
and policy order.
|
|
119
|
+
|
|
120
|
+
:param desired: the JSON array string to pass to ``put_parameter``
|
|
121
|
+
:param current: the ``Policies`` list of a ``describe_parameters`` item
|
|
122
|
+
|
|
123
|
+
:return: True only if both hold the same policies. Anything that cannot be
|
|
124
|
+
parsed counts as different, so the caller writes rather than skips.
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
desired_policies = json.loads(desired)
|
|
128
|
+
if isinstance(desired_policies, dict):
|
|
129
|
+
desired_policies = [desired_policies]
|
|
130
|
+
current_policies = []
|
|
131
|
+
for policy in current or []:
|
|
132
|
+
current_policies.append(json.loads(policy["PolicyText"]))
|
|
133
|
+
desired_keys = sorted(json.dumps(p, sort_keys=True) for p in desired_policies)
|
|
134
|
+
current_keys = sorted(json.dumps(p, sort_keys=True) for p in current_policies)
|
|
135
|
+
except (ValueError, KeyError, TypeError):
|
|
136
|
+
return False
|
|
137
|
+
return desired_keys == current_keys
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _is_parameter_changed(
|
|
141
|
+
ssm_client: "SSMClient",
|
|
142
|
+
before_param: Parameter,
|
|
143
|
+
value: str,
|
|
144
|
+
description: str | None = OPT,
|
|
145
|
+
type: str | None = OPT,
|
|
146
|
+
tier: str | None = OPT,
|
|
147
|
+
key_id: str | None = OPT,
|
|
148
|
+
allowed_pattern: str | None = OPT,
|
|
149
|
+
policies: str | None = OPT,
|
|
150
|
+
data_type: str | None = OPT,
|
|
151
|
+
) -> bool:
|
|
152
|
+
"""
|
|
153
|
+
Check whether an existing parameter differs from the desired state.
|
|
154
|
+
|
|
155
|
+
Only the arguments that are passed are compared; an argument left as ``OPT``
|
|
156
|
+
means "keep whatever is there". ``describe_parameters`` is called only when
|
|
157
|
+
one of the fields it alone returns is passed.
|
|
158
|
+
|
|
159
|
+
:param before_param: the current parameter, read with decryption
|
|
160
|
+
:param type: the parameter type as a plain string (e.g. "SecureString")
|
|
161
|
+
:param tier: the parameter tier as a plain string (e.g. "Standard")
|
|
162
|
+
|
|
163
|
+
:return: True if ``put_parameter`` has to be called.
|
|
164
|
+
"""
|
|
165
|
+
if value != before_param.value:
|
|
166
|
+
return True
|
|
167
|
+
if type is not OPT and type != before_param.type:
|
|
168
|
+
return True
|
|
169
|
+
if data_type is not OPT and data_type != before_param.data_type:
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
# These fields are only returned by describe_parameters
|
|
173
|
+
metadata_args = [description, tier, key_id, allowed_pattern, policies]
|
|
174
|
+
if all(arg is OPT for arg in metadata_args):
|
|
175
|
+
return False
|
|
176
|
+
metadata = _get_parameter_metadata(ssm_client, before_param.name)
|
|
177
|
+
if description is not OPT and description != metadata.get("Description", ""):
|
|
178
|
+
return True
|
|
179
|
+
# An Intelligent-Tiering parameter is stored as Standard or Advanced,
|
|
180
|
+
# so there is nothing to compare it with
|
|
181
|
+
if tier is not OPT and tier != ParameterTier.INTELLIGENT_TIERING.value:
|
|
182
|
+
if tier != metadata.get("Tier"):
|
|
183
|
+
return True
|
|
184
|
+
if key_id is not OPT and key_id != metadata.get("KeyId"):
|
|
185
|
+
return True
|
|
186
|
+
if allowed_pattern is not OPT:
|
|
187
|
+
if allowed_pattern != metadata.get("AllowedPattern", ""):
|
|
188
|
+
return True
|
|
189
|
+
if policies is not OPT:
|
|
190
|
+
if not _is_same_policies(policies, metadata.get("Policies")):
|
|
191
|
+
return True
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _put_parameter_if_changed(
|
|
196
|
+
ssm_client: "SSMClient",
|
|
197
|
+
name: str,
|
|
198
|
+
value: str,
|
|
199
|
+
description: str | None = OPT,
|
|
200
|
+
type: str | None = OPT,
|
|
201
|
+
tier: str | None = OPT,
|
|
202
|
+
key_id: str | None = OPT,
|
|
203
|
+
allowed_pattern: str | None = OPT,
|
|
204
|
+
tags: dict[str, str] | None = OPT,
|
|
205
|
+
policies: str | None = OPT,
|
|
206
|
+
data_type: str | None = OPT,
|
|
207
|
+
) -> tuple[Parameter | None, Parameter | None]:
|
|
208
|
+
"""
|
|
209
|
+
One read-compare-write round of :func:`put_parameter_if_changed`, with
|
|
210
|
+
``type`` and ``tier`` already converted to plain strings.
|
|
211
|
+
|
|
212
|
+
It raises the ``ParameterAlreadyExists`` error when another writer creates
|
|
213
|
+
the parameter between the read and the write.
|
|
214
|
+
"""
|
|
215
|
+
# Always decrypt: the flag is ignored for String and StringList, and a
|
|
216
|
+
# SecureString can only be compared by its plain value
|
|
217
|
+
before_param = get_parameter(ssm_client, name, with_decryption=True)
|
|
218
|
+
is_param_exists = before_param is not None
|
|
219
|
+
|
|
220
|
+
if is_param_exists:
|
|
221
|
+
should_write = _is_parameter_changed(
|
|
222
|
+
ssm_client=ssm_client,
|
|
223
|
+
before_param=before_param,
|
|
224
|
+
value=value,
|
|
225
|
+
description=description,
|
|
226
|
+
type=type,
|
|
227
|
+
tier=tier,
|
|
228
|
+
key_id=key_id,
|
|
229
|
+
allowed_pattern=allowed_pattern,
|
|
230
|
+
policies=policies,
|
|
231
|
+
data_type=data_type,
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
should_write = True
|
|
235
|
+
|
|
236
|
+
if should_write is False:
|
|
237
|
+
return before_param, None
|
|
238
|
+
|
|
239
|
+
kwargs = dict(
|
|
240
|
+
Name=name,
|
|
241
|
+
Value=value,
|
|
242
|
+
Description=description,
|
|
243
|
+
Type=type,
|
|
244
|
+
Tier=tier,
|
|
245
|
+
KeyId=key_id,
|
|
246
|
+
AllowedPattern=allowed_pattern,
|
|
247
|
+
Policies=policies,
|
|
248
|
+
DataType=data_type,
|
|
249
|
+
)
|
|
250
|
+
if is_param_exists:
|
|
251
|
+
# put_parameter does not accept Tags together with Overwrite;
|
|
252
|
+
# the caller syncs the tags of an existing parameter separately
|
|
253
|
+
kwargs["Overwrite"] = True
|
|
254
|
+
elif isinstance(tags, dict) and len(tags):
|
|
255
|
+
kwargs["Tags"] = encode_tags(tags)
|
|
256
|
+
# Without Overwrite, this fails with ParameterAlreadyExists instead of
|
|
257
|
+
# replacing a parameter that another writer has just created
|
|
258
|
+
response = ssm_client.put_parameter(**remove_optional(**kwargs))
|
|
259
|
+
|
|
260
|
+
# put_parameter only returns Version and Tier, so build the rest from the input
|
|
261
|
+
if type is OPT and is_param_exists:
|
|
262
|
+
type = before_param.type
|
|
263
|
+
param_data = dict(
|
|
264
|
+
Name=name,
|
|
265
|
+
Value=value,
|
|
266
|
+
Description=description,
|
|
267
|
+
Type=type,
|
|
268
|
+
Tier=tier,
|
|
269
|
+
)
|
|
270
|
+
param_data = remove_optional(**param_data)
|
|
271
|
+
param_data.update(response)
|
|
272
|
+
after_param = Parameter(_data=param_data)
|
|
273
|
+
return before_param, after_param
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def put_parameter_if_changed(
|
|
277
|
+
ssm_client: "SSMClient",
|
|
278
|
+
name: str,
|
|
279
|
+
value: str,
|
|
280
|
+
description: str | None = OPT,
|
|
281
|
+
type: ParameterType | None = OPT,
|
|
282
|
+
tier: ParameterTier | None = OPT,
|
|
283
|
+
key_id: str | None = OPT,
|
|
284
|
+
allowed_pattern: str | None = OPT,
|
|
285
|
+
tags: dict[str, str] | None = OPT,
|
|
286
|
+
policies: str | None = OPT,
|
|
287
|
+
data_type: str | None = OPT,
|
|
288
|
+
) -> tuple[Parameter | None, Parameter | None]:
|
|
289
|
+
"""
|
|
290
|
+
Make a parameter match the desired state, writing a new version only when
|
|
291
|
+
something actually changed (conditional write).
|
|
292
|
+
|
|
293
|
+
The function reads the parameter, compares it with the arguments you pass,
|
|
294
|
+
and calls ``put_parameter`` only if the parameter does not exist or differs.
|
|
295
|
+
Running it again with the same arguments writes nothing.
|
|
296
|
+
|
|
297
|
+
**Why This Matters - Version History Preservation:**
|
|
298
|
+
|
|
299
|
+
AWS SSM Parameter Store only retains the last 100 parameter versions. During
|
|
300
|
+
development, debugging, or frequent deployments, blindly calling put_parameter()
|
|
301
|
+
can rapidly consume this version history - even when values haven't actually
|
|
302
|
+
changed. This can make older parameter versions permanently inaccessible.
|
|
303
|
+
|
|
304
|
+
**What is compared:**
|
|
305
|
+
|
|
306
|
+
- ``value``, always. The current value is read with decryption, so a
|
|
307
|
+
``SecureString`` is compared by its plain value whether or not you pass
|
|
308
|
+
``type``. This needs ``kms:Decrypt`` on its KMS key.
|
|
309
|
+
- ``description``, ``type``, ``tier``, ``key_id``, ``allowed_pattern``,
|
|
310
|
+
``policies``, and ``data_type``, but only the ones you pass. An argument you
|
|
311
|
+
leave out keeps whatever the parameter has. ``tier=INTELLIGENT_TIERING``
|
|
312
|
+
is not compared, because AWS stores such a parameter as Standard or Advanced.
|
|
313
|
+
|
|
314
|
+
**Tags:**
|
|
315
|
+
|
|
316
|
+
When you pass ``tags``, they become the parameter's full tag set: on create
|
|
317
|
+
they are sent with ``put_parameter``; on an existing parameter they are
|
|
318
|
+
replaced with :func:`put_parameter_tags`, only if they differ. Tag changes
|
|
319
|
+
do not create a parameter version. Leave ``tags`` out to keep the tags as they are.
|
|
320
|
+
|
|
321
|
+
**Concurrency:**
|
|
322
|
+
|
|
323
|
+
The read and the write are separate calls, and SSM has no conditional write,
|
|
324
|
+
so this is a best effort. If another writer creates the parameter between
|
|
325
|
+
the read and the write, the create fails with ``ParameterAlreadyExists``;
|
|
326
|
+
the function then reads and compares once more. Two writers that update an
|
|
327
|
+
existing parameter at the same time can still both write: serialize such
|
|
328
|
+
writers outside this library.
|
|
329
|
+
|
|
330
|
+
Example usage::
|
|
331
|
+
|
|
332
|
+
# Create or update parameter only if value changed
|
|
333
|
+
before, after = put_parameter_if_changed(
|
|
334
|
+
ssm_client=client,
|
|
335
|
+
name="/app/database/host",
|
|
336
|
+
value="new-db-host.example.com",
|
|
337
|
+
type=ParameterType.STRING
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
if after is not None:
|
|
341
|
+
print(f"Parameter updated: version {after.version}")
|
|
342
|
+
else:
|
|
343
|
+
print("No update needed - value unchanged")
|
|
344
|
+
|
|
345
|
+
Return value interpretation:
|
|
346
|
+
|
|
347
|
+
- ``(None, Parameter)``: Parameter didn't exist, was created
|
|
348
|
+
- ``(Parameter, None)``: Parameter already matched, nothing was written
|
|
349
|
+
- ``(Parameter, Parameter)``: Parameter differed, a new version was written
|
|
350
|
+
|
|
351
|
+
Ref:
|
|
352
|
+
|
|
353
|
+
- `put_parameter <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.put_parameter>`_
|
|
354
|
+
|
|
355
|
+
:param ssm_client: SSM client
|
|
356
|
+
:param name: parameter name (e.g., "/app/database/host")
|
|
357
|
+
:param value: parameter value to set
|
|
358
|
+
:param description: parameter description
|
|
359
|
+
:param type: parameter type (String, StringList, SecureString)
|
|
360
|
+
:param tier: parameter tier (Standard, Advanced, Intelligent-Tiering)
|
|
361
|
+
:param key_id: KMS key ID for SecureString encryption
|
|
362
|
+
:param allowed_pattern: regex pattern for parameter validation
|
|
363
|
+
:param tags: the full tag set of the parameter, as key-value pairs
|
|
364
|
+
:param policies: parameter policies (JSON string)
|
|
365
|
+
:param data_type: parameter data type (e.g., "text", "aws:ec2:image")
|
|
366
|
+
|
|
367
|
+
:returns: Tuple of (before_parameter, after_parameter) where:
|
|
368
|
+
- before_parameter: Parameter object before operation (None if didn't exist)
|
|
369
|
+
- after_parameter: Parameter object after operation (None if no write occurred)
|
|
370
|
+
"""
|
|
371
|
+
kwargs = dict(
|
|
372
|
+
ssm_client=ssm_client,
|
|
373
|
+
name=name,
|
|
374
|
+
value=value,
|
|
375
|
+
description=description,
|
|
376
|
+
type=type.value if isinstance(type, ParameterType) else type,
|
|
377
|
+
tier=tier.value if isinstance(tier, ParameterTier) else tier,
|
|
378
|
+
key_id=key_id,
|
|
379
|
+
allowed_pattern=allowed_pattern,
|
|
380
|
+
tags=tags,
|
|
381
|
+
policies=policies,
|
|
382
|
+
data_type=data_type,
|
|
383
|
+
)
|
|
384
|
+
try:
|
|
385
|
+
before_param, after_param = _put_parameter_if_changed(**kwargs)
|
|
386
|
+
except botocore.exceptions.ClientError as e:
|
|
387
|
+
if e.response["Error"]["Code"] != "ParameterAlreadyExists":
|
|
388
|
+
raise # pragma: no cover
|
|
389
|
+
# Another writer created it between our read and write: compare again
|
|
390
|
+
before_param, after_param = _put_parameter_if_changed(**kwargs)
|
|
391
|
+
|
|
392
|
+
if isinstance(tags, dict) and before_param is not None:
|
|
393
|
+
if get_parameter_tags(ssm_client, name) != tags:
|
|
394
|
+
put_parameter_tags(ssm_client, name, tags)
|
|
395
|
+
|
|
396
|
+
return before_param, after_param
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def delete_parameter(
|
|
400
|
+
ssm_client: "SSMClient",
|
|
401
|
+
name: str,
|
|
402
|
+
) -> bool:
|
|
403
|
+
"""
|
|
404
|
+
Delete a parameter by name with idempotent behavior.
|
|
405
|
+
|
|
406
|
+
This function provides idempotent parameter deletion - it can be called multiple
|
|
407
|
+
times safely without raising errors. Unlike the raw boto3 client which raises
|
|
408
|
+
ParameterNotFound exceptions, this function returns False for non-existent
|
|
409
|
+
parameters, making it safe to use in cleanup operations and automation scripts.
|
|
410
|
+
|
|
411
|
+
The idempotent behavior ensures that:
|
|
412
|
+
|
|
413
|
+
- If the parameter exists, it will be deleted and return True
|
|
414
|
+
- If the parameter doesn't exist, it returns False without raising an error
|
|
415
|
+
- Multiple calls with the same parameter name are safe
|
|
416
|
+
|
|
417
|
+
Example usage in cleanup scripts::
|
|
418
|
+
|
|
419
|
+
# Safe to call even if parameter doesn't exist
|
|
420
|
+
deleted = delete_parameter(client, "/app/temp/config")
|
|
421
|
+
if deleted:
|
|
422
|
+
print("Parameter was deleted")
|
|
423
|
+
else:
|
|
424
|
+
print("Parameter did not exist")
|
|
425
|
+
|
|
426
|
+
Ref:
|
|
427
|
+
|
|
428
|
+
- `delete_parameter <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.delete_parameter>`_
|
|
429
|
+
|
|
430
|
+
:param ssm_client: SSM client
|
|
431
|
+
:param name: parameter name (e.g., "/app/database/host")
|
|
432
|
+
|
|
433
|
+
:return: True if the parameter was deleted, False if it did not exist.
|
|
434
|
+
"""
|
|
435
|
+
try:
|
|
436
|
+
ssm_client.delete_parameter(Name=name)
|
|
437
|
+
return True
|
|
438
|
+
except botocore.exceptions.ClientError as e:
|
|
439
|
+
if e.response["Error"]["Code"] == "ParameterNotFound":
|
|
440
|
+
return False
|
|
441
|
+
raise # pragma: no cover
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def get_parameter_tags(
|
|
445
|
+
ssm_client: "SSMClient",
|
|
446
|
+
name: str,
|
|
447
|
+
) -> dict[str, str]:
|
|
448
|
+
"""
|
|
449
|
+
Get all tags associated with a parameter.
|
|
450
|
+
|
|
451
|
+
This function retrieves all tags for the specified parameter and returns them
|
|
452
|
+
as a convenient key-value dictionary. If the parameter has no tags, an empty
|
|
453
|
+
dictionary is returned.
|
|
454
|
+
|
|
455
|
+
Ref:
|
|
456
|
+
|
|
457
|
+
- `list_tags_for_resource <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.list_tags_for_resource>`_
|
|
458
|
+
|
|
459
|
+
:param ssm_client: SSM client
|
|
460
|
+
:param name: parameter name (e.g., "/app/database/host")
|
|
461
|
+
|
|
462
|
+
:return: Dictionary of tag key-value pairs. Empty dict if parameter has no tags.
|
|
463
|
+
"""
|
|
464
|
+
response = ssm_client.list_tags_for_resource(
|
|
465
|
+
ResourceType=ResourceType.PARAMETER.value,
|
|
466
|
+
ResourceId=name,
|
|
467
|
+
)
|
|
468
|
+
return decode_tags(response.get("TagList", []))
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def remove_parameter_tags(
|
|
472
|
+
ssm_client: "SSMClient",
|
|
473
|
+
name: str,
|
|
474
|
+
tag_keys: list[str],
|
|
475
|
+
):
|
|
476
|
+
"""
|
|
477
|
+
Remove specific tags from a parameter by tag keys.
|
|
478
|
+
|
|
479
|
+
This function removes only the specified tag keys from the parameter, leaving
|
|
480
|
+
other tags unchanged. It's useful for selective tag cleanup without affecting
|
|
481
|
+
the entire tag set.
|
|
482
|
+
|
|
483
|
+
Example::
|
|
484
|
+
|
|
485
|
+
# Remove only the "Environment" and "Team" tags
|
|
486
|
+
remove_parameter_tags(client, "/app/config", ["Environment", "Team"])
|
|
487
|
+
|
|
488
|
+
Ref:
|
|
489
|
+
|
|
490
|
+
- `remove_tags_from_resource <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.remove_tags_from_resource>`_
|
|
491
|
+
|
|
492
|
+
:param ssm_client: SSM client
|
|
493
|
+
:param name: parameter name (e.g., "/app/database/host")
|
|
494
|
+
:param tag_keys: list of tag keys to remove (e.g., ["Environment", "Team"])
|
|
495
|
+
"""
|
|
496
|
+
return ssm_client.remove_tags_from_resource(
|
|
497
|
+
ResourceType=ResourceType.PARAMETER.value,
|
|
498
|
+
ResourceId=name,
|
|
499
|
+
TagKeys=tag_keys,
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def update_parameter_tags(
|
|
504
|
+
ssm_client: "SSMClient",
|
|
505
|
+
name: str,
|
|
506
|
+
tags: dict[str, str],
|
|
507
|
+
):
|
|
508
|
+
"""
|
|
509
|
+
Add or update specific tags on a parameter (partial update).
|
|
510
|
+
|
|
511
|
+
This function performs a partial update of parameter tags. It adds new tags
|
|
512
|
+
and updates existing tags with the provided key-value pairs, while leaving
|
|
513
|
+
other existing tags unchanged. This is useful for adding metadata without
|
|
514
|
+
affecting the entire tag set.
|
|
515
|
+
|
|
516
|
+
Example::
|
|
517
|
+
|
|
518
|
+
# Add/update only specific tags, keeping others intact
|
|
519
|
+
update_parameter_tags(client, "/app/config", {
|
|
520
|
+
"Environment": "production",
|
|
521
|
+
"LastUpdated": "2024-01-15"
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
Ref:
|
|
525
|
+
|
|
526
|
+
- `add_tags_to_resource <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.add_tags_to_resource>`_
|
|
527
|
+
|
|
528
|
+
:param ssm_client: SSM client
|
|
529
|
+
:param name: parameter name (e.g., "/app/database/host")
|
|
530
|
+
:param tags: dictionary of tag key-value pairs to add/update
|
|
531
|
+
"""
|
|
532
|
+
return ssm_client.add_tags_to_resource(
|
|
533
|
+
ResourceType=ResourceType.PARAMETER.value,
|
|
534
|
+
ResourceId=name,
|
|
535
|
+
Tags=encode_tags(tags),
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def put_parameter_tags(
|
|
540
|
+
ssm_client: "SSMClient",
|
|
541
|
+
name: str,
|
|
542
|
+
tags: dict[str, str],
|
|
543
|
+
):
|
|
544
|
+
"""
|
|
545
|
+
Replace all parameter tags with the provided tag set (full replacement).
|
|
546
|
+
|
|
547
|
+
This function performs a complete replacement of all parameter tags. It removes
|
|
548
|
+
all existing tags and replaces them with the provided tags. If an empty
|
|
549
|
+
dictionary is provided, all tags are removed from the parameter.
|
|
550
|
+
|
|
551
|
+
Behavior:
|
|
552
|
+
|
|
553
|
+
- Empty dict: Remove all existing tags from the parameter
|
|
554
|
+
- Non-empty dict: Replace all existing tags with the provided tags
|
|
555
|
+
|
|
556
|
+
Example::
|
|
557
|
+
|
|
558
|
+
# Replace all tags with new set
|
|
559
|
+
put_parameter_tags(client, "/app/config", {
|
|
560
|
+
"Environment": "production",
|
|
561
|
+
"Owner": "platform-team"
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
# Remove all tags
|
|
565
|
+
put_parameter_tags(client, "/app/config", {})
|
|
566
|
+
|
|
567
|
+
Ref:
|
|
568
|
+
|
|
569
|
+
- `add_tags_to_resource <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.add_tags_to_resource>`_
|
|
570
|
+
|
|
571
|
+
:param ssm_client: SSM client
|
|
572
|
+
:param name: parameter name (e.g., "/app/database/host")
|
|
573
|
+
:param tags: dictionary of tag key-value pairs to set (empty dict removes all tags)
|
|
574
|
+
"""
|
|
575
|
+
existing_tags = get_parameter_tags(ssm_client, name)
|
|
576
|
+
|
|
577
|
+
if len(tags) == 0:
|
|
578
|
+
if len(existing_tags): # only run remove tags when there are existing tags
|
|
579
|
+
remove_parameter_tags(ssm_client, name, list(existing_tags))
|
|
580
|
+
else:
|
|
581
|
+
# if to-update tags is super set of the existing tags
|
|
582
|
+
# then no need to run remove tags
|
|
583
|
+
# otherwise, need to run remove tags
|
|
584
|
+
if not (len(set(existing_tags).difference(set(tags))) == 0):
|
|
585
|
+
remove_parameter_tags(ssm_client, name, list(existing_tags))
|
|
586
|
+
ssm_client.add_tags_to_resource(
|
|
587
|
+
ResourceType=ResourceType.PARAMETER.value,
|
|
588
|
+
ResourceId=name,
|
|
589
|
+
Tags=encode_tags(tags),
|
|
590
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ParameterType(str, enum.Enum):
|
|
7
|
+
"""
|
|
8
|
+
See `What is a parameter? <https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html#what-is-a-parameter>`_
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
STRING = "String"
|
|
12
|
+
STRING_LIST = "StringList"
|
|
13
|
+
SECURE_STRING = "SecureString"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ParameterTier(str, enum.Enum):
|
|
17
|
+
"""
|
|
18
|
+
See `Parameter tiers <https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-advanced-parameters.html>`_
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
STANDARD = "Standard"
|
|
22
|
+
ADVANCED = "Advanced"
|
|
23
|
+
INTELLIGENT_TIERING = "Intelligent-Tiering"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ResourceType(str, enum.Enum):
|
|
27
|
+
"""
|
|
28
|
+
Resource type parameter value enum for:
|
|
29
|
+
|
|
30
|
+
- `list_tags_for_resource <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.list_tags_for_resource>`_
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
DOCUMENT = "Document"
|
|
34
|
+
MANAGED_INSTANCE = "ManagedInstance"
|
|
35
|
+
MAINTENANCE_WINDOW = "MaintenanceWindow"
|
|
36
|
+
PARAMETER = "Parameter"
|
|
37
|
+
PATCH_BASELINE = "PatchBaseline"
|
|
38
|
+
OPS_ITEM = "OpsItem"
|
|
39
|
+
OPS_METADATA = "OpsMetadata"
|
|
40
|
+
AUTOMATION = "Automation"
|
|
41
|
+
ASSOCIATION = "Association"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
DEFAULT_KMS_KEY = "alias/aws/ssm"
|