Skip to content

Commit dc79d22

Browse files
Feature/ivia11 features (#442)
* refactor: add pylintrc * refactor: update pylintrc to disable some checks for now * fix: update container repo code (user and secret are NOT required values, although it is stated like that in the documentation) * fix: add empty version (test) * fix: update import submodules call (try) * fix: make idempotent * fix: small correction * fix: update admin.py for new parameters in IVIA11 * fix: code improvement updates flake8 * feature: add label for v11 * fix: tiny update * documentation: update versions and changelog * documentation: update license related to PEP 639
1 parent 95fd280 commit dc79d22

11 files changed

Lines changed: 300 additions & 337 deletions

File tree

changelog.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Manual change log
22

3+
## Latest
4+
5+
6+
## 2025.3.28.0
7+
8+
- fix: base/extensions.py - improve idempotency #441
9+
- fix: base/container_ext/repo.py - user and secret are not required (although documentation states they are)
10+
- feature: base/admin.py - improve idempotency, support for new parameters in IVIA 11
11+
- feature: base/ssl_certificates/personal_certificate.py - support label parameter in IVIA 11
12+
- PEP 639: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license
13+
314
## 2025.3.14.0
415

516
- build: add pylint configuration

ibmsecurity/isam/base/admin.py

Lines changed: 158 additions & 267 deletions
Large diffs are not rendered by default.

ibmsecurity/isam/base/container_ext/repo.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from ibmsecurity.utilities import tools
23

34
logger = logging.getLogger(__name__)
45

@@ -37,17 +38,17 @@ def get(isamAppliance, registry_id, check_mode=False, force=False):
3738
def add(
3839
isamAppliance,
3940
registry,
40-
user,
41-
secret,
41+
user=None,
42+
secret=None,
4243
**kwargs,
4344
):
4445
"""
4546
Add a credential for a user and container registry
4647
4748
:param isamAppliance:
4849
:param registry: hostname of the registry, eg. icr.io
49-
:param user: user
50-
:param secret: the secret for the user
50+
:param user: user (optional)
51+
:param secret: the secret for the user (optional)
5152
:param proxy_host (optional)
5253
:param proxy_port (optional)
5354
:param proxy_user (optional)
@@ -99,12 +100,13 @@ def add(
99100
def update(
100101
isamAppliance,
101102
registry,
102-
user,
103-
secret,
103+
user=None,
104+
secret=None,
104105
**kwargs,
105106
):
106107
"""
107108
Update a credential for a user and container registry
109+
(user and secret are NOT required values, although it is stated like that in the documentation)
108110
109111
:param isamAppliance:
110112
:param registry: hostname of the registry, eg. icr.io
@@ -134,13 +136,26 @@ def update(
134136
if check_mode:
135137
return isamAppliance.create_return_object(changed=True, warnings=warnings)
136138
else:
137-
put_data = {
138-
"host": registry,
139-
"user": user,
140-
"secret": secret,
141-
}
142-
143-
# put_data.update(input_args)
139+
if tools.version_compare(isamAppliance.facts["version"], "10.0.9.0") < 0:
140+
warnings.append(
141+
"Appliance is at version: {0}. Using the previous format for container repo.".format(
142+
isamAppliance.facts["version"]
143+
)
144+
)
145+
put_data = {
146+
"host": registry,
147+
"user": user,
148+
}
149+
150+
else:
151+
put_data = {
152+
"host": registry,
153+
"user": user,
154+
}
155+
# append other fields from input parameters
156+
put_data.update(input_args)
157+
if secret is not None:
158+
put_data['secret'] = secret
144159

145160
return isamAppliance.invoke_put(
146161
"Update registry",
@@ -157,17 +172,18 @@ def update(
157172
def set(
158173
isamAppliance,
159174
registry,
160-
user,
161-
secret,
175+
user=None,
176+
secret=None,
162177
**kwargs,
163178
):
164179
"""
165180
Set - we can't make this function idempotent if a secret is set.
181+
(user and secret are NOT required values, although it is stated like that in the documentation)
166182
TODO: execute every time ?
167183
"""
168184
if _check(isamAppliance, registry, user):
169185
logger.debug(f"\nUpdating {registry} with {user}")
170-
186+
# TODO: make this idempotent
171187
return update(
172188
isamAppliance,
173189
registry,
@@ -185,7 +201,7 @@ def set(
185201
**kwargs)
186202

187203

188-
def search(isamAppliance, host, user, check_mode=False, force=False):
204+
def search(isamAppliance, host, user=None, check_mode=False, force=False):
189205
"""
190206
Return the id of the repository
191207
@@ -200,10 +216,16 @@ def search(isamAppliance, host, user, check_mode=False, force=False):
200216
return_obj = isamAppliance.create_return_object()
201217
return_obj["data"] = None
202218
for obj in ret_obj["data"]:
203-
if obj.get("host", "") == host and obj.get("user", "") == user:
204-
return_obj["data"] = obj["id"]
205-
return_obj["rc"] = 0
206-
break
219+
if user is None:
220+
if obj.get("host", "") == host:
221+
return_obj["data"] = obj["id"]
222+
return_obj["rc"] = 0
223+
break
224+
else:
225+
if obj.get("host", "") == host and obj.get("user", "") == user:
226+
return_obj["data"] = obj["id"]
227+
return_obj["rc"] = 0
228+
break
207229

208230
return return_obj
209231

@@ -223,10 +245,9 @@ def _check(isamAppliance, registry, user):
223245
return False
224246

225247

226-
def delete(isamAppliance, registry, user, check_mode=False, force=False):
248+
def delete(isamAppliance, registry, user=None, check_mode=False, force=False):
227249
"""
228-
Delete a repo by host and
229-
./testisam_cmd.py --hostname=${ISAM_LMI} --method=ibmsecurity.isam.base.container_ext.repo.delete --method_options="name=icr.io/isva/verify-access-oidc-provider:23.03"
250+
Delete a repo by host (and user)
230251
"""
231252
ret_obj = search(isamAppliance, registry, user)
232253
registry_id = ret_obj.get("data", None)

ibmsecurity/isam/base/extensions.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,26 @@ def get_all(isamAppliance, check_mode=False, force=False):
2828
)
2929

3030

31-
def get(isamAppliance, extId, check_mode=False, force=False):
31+
def get(isamAppliance, extId, detailed=False, check_mode=False, force=False):
3232
"""
3333
Retrieve installed extension by id
3434
"""
35-
ret_obj = isamAppliance.create_return_object()
36-
extensions = get_all(isamAppliance)
37-
for obj in extensions["data"]:
38-
if obj["id"] == extId:
39-
ret_obj["data"] = obj
40-
break
41-
return ret_obj
35+
if detailed:
36+
ret_obj = isamAppliance.invoke_get(
37+
"Retrieve details about installed extension",
38+
f"{uri}/{extId}",
39+
requires_modules=requires_modules,
40+
requires_version=requires_version,
41+
)
42+
return ret_obj
43+
else:
44+
ret_obj = isamAppliance.create_return_object()
45+
extensions = get_all(isamAppliance)
46+
for obj in extensions["data"]:
47+
if obj["id"] == extId:
48+
ret_obj["data"] = obj
49+
break
50+
return ret_obj
4251

4352

4453
def add(
@@ -137,6 +146,20 @@ def update(
137146
return isamAppliance.create_return_object(changed=True)
138147
else:
139148
config_str = _get_config_data(extId, config_data)
149+
logger.debug("\nCONFIGDATA from input\n" + config_str)
150+
if not force:
151+
# Compare . It's not possible to compare the extension (gui_json) itself, since we don't expect the extension here
152+
currentConfigData = get(isamAppliance, extId=extId, detailed=True)
153+
currentConfigData = currentConfigData.get('data', {'config_data': ''})
154+
currentConfigData = currentConfigData.get('config_data', '')
155+
156+
currentConfigDataStr = json.dumps(currentConfigData, skipkeys=True, sort_keys=True)
157+
158+
logger.debug("\nCURRENT CONFIGDATA\n" + currentConfigDataStr)
159+
160+
if config_str == currentConfigDataStr:
161+
return isamAppliance.create_return_object(changed=False)
162+
140163
files = {}
141164
files["config_data"] = (None, config_str)
142165

@@ -183,14 +206,15 @@ def set(
183206
):
184207

185208
# MUST have an extId to do an update.
209+
# Also, some extensions do not like updates, they require delete/add
186210
if extId and search(isamAppliance, extId):
187211
return update(
188212
isamAppliance=isamAppliance,
189213
extId=extId,
190214
config_data=config_data,
191215
third_party_package=third_party_package,
192216
check_mode=check_mode,
193-
force=True,
217+
force=force,
194218
)
195219
else:
196220
return add(
@@ -275,7 +299,7 @@ def _get_config_data(extId, config_data):
275299
return '{"extId": "' + extId + '",' + config_data + "}"
276300
else:
277301
config_data["extId"] = extId
278-
return json.dumps(config_data)
302+
return json.dumps(config_data, skipkeys=True, sort_keys=True)
279303

280304

281305
def search(isamAppliance, extId, check_mode=False, force=False):

ibmsecurity/isam/base/runtime/tuning_parameters.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,18 @@
44
requires_modules = ["mga", "federation"]
55
requires_model = "Appliance"
66

7+
78
def get(isamAppliance, check_mode=False, force=False):
89
"""
910
Get runtime tuning settings
1011
"""
1112
return isamAppliance.invoke_get("Retrieving runtime tuning parameters",
1213
"/mga/runtime_tuning/v1",
13-
requires_modules=requires_modules,requires_model=requires_model)
14+
requires_modules=requires_modules,
15+
requires_model=requires_model)
1416

1517

1618
def set(isamAppliance, option, value, check_mode=False, force=False):
17-
18-
# ret_obj = get(isamAppliance)
19-
# for key, val in ret_obj.items():
20-
# if key == 'warnings' and val != []:
21-
# if "Docker" in val[0]:
22-
# return isamAppliance.create_return_object(warnings=ret_obj['warnings'])
2319
"""
2420
Set a runtime tuning parameter
2521
"""
@@ -33,14 +29,14 @@ def set(isamAppliance, option, value, check_mode=False, force=False):
3329

3430
if force is True or matches is False:
3531
if check_mode is True:
36-
return isamAppliance.create_return_object(changed=True,warnings=warnings)
32+
return isamAppliance.create_return_object(changed=True, warnings=warnings)
3733
else:
3834
return isamAppliance.invoke_put(
3935
"Setting a runtime tuning parameter",
4036
"/mga/runtime_tuning/{0}/v1".format(option),
4137
{
4238
'value': value
43-
}, requires_modules=requires_modules,requires_model=requires_model)
39+
}, requires_modules=requires_modules, requires_model=requires_model)
4440

4541
return isamAppliance.create_return_object(warnings=warnings)
4642

@@ -62,7 +58,7 @@ def _check(isamAppliance, option, value):
6258
logger.info(
6359
"Tuning parameter {0} value does not match {1} != {2}.".format(option, ret_obj['data'][option], value))
6460
exists = True
65-
except:
61+
except Exception:
6662
logger.info("Runtime tuning parameter does not exist")
6763

6864
return matches, exists, warnings
@@ -78,14 +74,14 @@ def reset(isamAppliance, option, check_mode=False, force=False):
7874
if force is False:
7975
matches, exists, warnings = _check(isamAppliance, option, None)
8076

81-
if force is True or exists is True:
77+
if force is True or exists:
8278
if check_mode is True:
83-
return isamAppliance.create_return_object(changed=True,warnings=warnings)
79+
return isamAppliance.create_return_object(changed=True, warnings=warnings)
8480
else:
8581
return isamAppliance.invoke_delete(
8682
"Reset a runtime tuning parameter to default value",
8783
"/mga/runtime_tuning/{0}/v1".format(option),
88-
requires_modules=requires_modules,requires_model=requires_model)
84+
requires_modules=requires_modules, requires_model=requires_model)
8985
elif exists is False and warnings == []:
9086
warnings.append("Tuning Parameter {0} was not found. Skipping reset() request.".format(option))
9187

ibmsecurity/isam/base/ssl_certificates/personal_certificate.py

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ def set(isamAppliance, kdb_id, cert_id, default='no', check_mode=False, force=Fa
107107
f"Appliance is at version: {isamAppliance.facts['version']}. Setting certificates as default is no longer supported."
108108
)
109109
else:
110-
if force is True or _check_default(isamAppliance, kdb_id, cert_id, default):
111-
if check_mode is True:
110+
if force or _check_default(isamAppliance, kdb_id, cert_id, default):
111+
if check_mode:
112112
return isamAppliance.create_return_object(changed=True)
113113
else:
114114
return isamAppliance.invoke_put(
@@ -151,13 +151,13 @@ def delete(isamAppliance, kdb_id, cert_id, check_mode=False, force=False):
151151
"""
152152
Deleting a personal certificate from a certificate database
153153
"""
154-
if force is True or _check(isamAppliance, kdb_id, cert_id) is True:
155-
if check_mode is True:
154+
if force or _check(isamAppliance, kdb_id, cert_id):
155+
if check_mode:
156156
return isamAppliance.create_return_object(changed=True)
157157
else:
158158
return isamAppliance.invoke_delete(
159159
"Deleting a personal certificate from a certificate database",
160-
"/isam/ssl_certificates/{0}/personal_cert/{1}".format(kdb_id, cert_id))
160+
f"/isam/ssl_certificates/{kdb_id}/personal_cert/{cert_id}")
161161

162162
return isamAppliance.create_return_object()
163163

@@ -179,26 +179,39 @@ def export_cert(isamAppliance, kdb_id, cert_id, filename, check_mode=False, forc
179179
def import_cert(isamAppliance, kdb_id, cert, label=None, password=None, check_mode=False, force=False):
180180
"""
181181
Importing a personal certificate into a certificate database
182-
Remark: you can add a label, but it's only used for a half-hearted check if the certificate already exists.
182+
Remark: you can add a label, but it's only used for a half-hearted check if the certificate already exists for versions < 11
183183
"""
184-
if force is True or not _check(isamAppliance, kdb_id, label):
185-
if check_mode is True:
184+
warnings = []
185+
if force or not _check(isamAppliance, kdb_id, label):
186+
if check_mode:
186187
return isamAppliance.create_return_object(changed=True)
187188
else:
189+
iviaVersion = isamAppliance.facts["version"]
190+
post_data = {
191+
'file_formfield': 'cert',
192+
'filename': cert,
193+
'mimetype': 'application/octet-stream'
194+
}
195+
json_data = {
196+
'operation': 'import'
197+
}
198+
if password is not None:
199+
json_data['password'] = password
200+
if label is not None:
201+
if ibmsecurity.utilities.tools.version_compare(iviaVersion, "10.0.9.0") < 0:
202+
warnings.append(f"Appliance at version: {iviaVersion}, label: {label} is not supported. Needs 10.0.9 or higher. Ignoring.")
203+
else:
204+
logger.debug(f"Adding label: {label}")
205+
json_data['label'] = label
206+
logger.debug(f"Posting json data: {json_data}")
188207
return isamAppliance.invoke_post_files(
189208
"Importing a personal certificate into a certificate database",
190-
"/isam/ssl_certificates/{0}/personal_cert".format(kdb_id),
209+
f"/isam/ssl_certificates/{kdb_id}/personal_cert",
191210
[
192-
{
193-
'file_formfield': 'cert',
194-
'filename': cert,
195-
'mimetype': 'application/octet-stream'
196-
}
211+
post_data
197212
],
198-
{
199-
'password': password,
200-
'operation': 'import'
201-
}
213+
json_data,
214+
warnings=warnings
202215
)
203216

204217
return isamAppliance.create_return_object()

ibmsecurity/isam/docker/_version.py

Whitespace-only changes.

0 commit comments

Comments
 (0)