Skip to content

Commit c4bd541

Browse files
Merge pull request #173 from bradmwilliams/issue-170
Allowing **kwargs to be passed to modify_and_apply()
2 parents 46829fe + bbdcec0 commit c4bd541

File tree

4 files changed

+132
-10
lines changed

4 files changed

+132
-10
lines changed

ansible/rebuild_module.digest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
40a6f692df33c354be1fae814d8429d5 -
1+
a47abb7772c34b91b10eab8657dabc12 -

ansible/roles/openshift_client_python/library/openshift_client_python.py

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/modify_and_apply.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/python
2+
3+
import argparse
4+
import json
5+
import logging
6+
import sys
7+
import traceback
8+
9+
import openshift_client as oc
10+
from openshift_client import OpenShiftPythonException
11+
from openshift_client.decorators import ephemeral_project
12+
13+
logging.basicConfig(level=logging.INFO, stream=sys.stdout, format='%(message)s')
14+
logger = logging.getLogger('ModifyAndApply')
15+
16+
17+
def validate_server_connection(ctx):
18+
with oc.options(ctx), oc.tracking(), oc.timeout(60):
19+
try:
20+
username = oc.whoami()
21+
version = oc.get_server_version()
22+
logger.debug(f'Connected to APIServer running version: {version}, as: {username}')
23+
except (ValueError, OpenShiftPythonException, Exception) as e:
24+
logger.error(f"Unable to verify cluster connection using context: \"{ctx['context']}\"")
25+
raise e
26+
27+
28+
def test_update_dynamic_keyword_args(obj):
29+
def update_dynamic_keyword_args(apiobj, **kwargs):
30+
logger.info(f'Updating object: {apiobj.name()} with: {json.dumps(kwargs, indent=4, default=str)}')
31+
return False
32+
33+
r, success = obj.modify_and_apply(update_dynamic_keyword_args, retries=0)
34+
assert len(r.actions()) == 0
35+
assert success == False
36+
37+
r, success = obj.modify_and_apply(update_dynamic_keyword_args, retries=0, param1='foo')
38+
assert len(r.actions()) == 0
39+
assert success == False
40+
41+
r, success = obj.modify_and_apply(update_dynamic_keyword_args, retries=0, param1='foo', param2='bar')
42+
assert len(r.actions()) == 0
43+
assert success == False
44+
45+
r, success = obj.modify_and_apply(update_dynamic_keyword_args, retries=0, random1='foo', modnar1='bar')
46+
assert len(r.actions()) == 0
47+
assert success == False
48+
49+
50+
def test_update_named_keyword_args(obj):
51+
def update_named_keyword_args(apiobj, param1=None, param2=None):
52+
logger.info(f'Updating object: {apiobj.name()} with "param1={param1}" and "param2={param2}"')
53+
return False
54+
55+
r, success = obj.modify_and_apply(update_named_keyword_args, retries=0)
56+
assert len(r.actions()) == 0
57+
assert success == False
58+
59+
r, success = obj.modify_and_apply(update_named_keyword_args, retries=0, param1='foo')
60+
assert len(r.actions()) == 0
61+
assert success == False
62+
63+
r, success = obj.modify_and_apply(update_named_keyword_args, retries=0, param1='foo', param2='bar')
64+
assert len(r.actions()) == 0
65+
assert success == False
66+
67+
try:
68+
obj.modify_and_apply(update_named_keyword_args, retries=0, param3='bip')
69+
except TypeError as e:
70+
if 'got an unexpected keyword argument' in e.__str__():
71+
logger.info(f'Unknown parameter specified: {e}')
72+
else:
73+
raise e
74+
75+
76+
@ephemeral_project
77+
def run(*, project_name=None):
78+
logger.info('Running in namespace: {}'.format(project_name))
79+
obj = oc.selector('serviceaccount/default').object()
80+
test_update_named_keyword_args(obj)
81+
test_update_dynamic_keyword_args(obj)
82+
83+
84+
if __name__ == '__main__':
85+
parser = argparse.ArgumentParser(description='Backup namespace resources')
86+
87+
config_group = parser.add_argument_group('Configuration Options')
88+
config_group.add_argument('-v', '--verbose', help='Enable verbose output', action='store_true')
89+
90+
ocp_group = parser.add_argument_group('Openshift Cluster Configuration Options')
91+
ocp_group.add_argument('-c', '--context', help='The OC context to use', default=None)
92+
ocp_group.add_argument('-k', '--kubeconfig', help='The kubeconfig to use (default is "~/.kube/config")', default=None)
93+
ocp_group.add_argument('-n', '--namespace', help='The namespace to process', default=None)
94+
95+
args = vars(parser.parse_args())
96+
97+
if args['verbose']:
98+
logger.setLevel(logging.DEBUG)
99+
100+
# Validate the connection to the respective cluster
101+
context = {}
102+
if args['context'] is not None:
103+
context.update({'context': args['context']})
104+
105+
if args['kubeconfig'] is not None:
106+
context.update({'kubeconfig': args['kubeconfig']})
107+
108+
validate_server_connection(context)
109+
110+
with oc.client_host():
111+
with oc.timeout(60 * 10), oc.tracking() as t:
112+
with oc.options(context):
113+
try:
114+
run()
115+
except (ValueError, OpenShiftPythonException, Exception):
116+
# Print out exception stack trace via the traceback module
117+
logger.info('Traceback output:\n{}\n'.format(traceback.format_exc()))
118+
119+
# Print out all oc interactions and do not redact secret information
120+
logger.info("OC tracking output:\n{}\n".format(t.get_result().as_json(redact_streams=False)))

packages/openshift_client/apiobject.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
from __future__ import absolute_import
22

3-
import yaml
4-
import sys
53
import copy
4+
import sys
65

6+
import yaml
7+
8+
from . import util
79
from .action import *
10+
from .context import cur_context
811
from .model import *
9-
from .result import *
1012
from .naming import kind_matches
11-
from .context import cur_context
13+
from .result import *
1214
from .selector import selector
13-
from . import util
1415

1516
_DEFAULT = object()
1617

@@ -467,17 +468,18 @@ def print_logs(self, stream=sys.stderr, timestamps=False, previous=False, since=
467468
self.logs(timestamps=timestamps, previous=previous, since=since, limit_bytes=limit_bytes,
468469
tail=tail, try_longshots=try_longshots, cmd_args=cmd_args))
469470

470-
def modify_and_apply(self, modifier_func, retries=2, cmd_args=None):
471+
def modify_and_apply(self, modifier_func, retries=2, cmd_args=None, **kwargs):
471472
"""
472473
Calls the modifier_func with self. The function should modify the model of the apiobj argument
473474
and return True if it wants this method to try to apply the change via the API. For robust
474475
implementations, a non-zero number of retries is recommended.
475476
476-
:param modifier_func: Called before each attempt with an self. The associated model will be refreshed before
477+
:param modifier_func: Called before each attempt with a self. The associated model will be refreshed before
477478
each call if necessary. If the function finds changes it wants to make to the model, it should
478479
make them directly and return True. If it does not want to make changes, it should return False.
479480
:param cmd_args: An optional list of additional arguments to pass on the command line
480481
:param retries: The number of times to retry. A value of 0 means only one attempt will be made.
482+
:param kwargs: keyword arguments passed directly into modifier_func
481483
:return: A Result object containing a record of all attempts AND a boolean. The boolean indicates
482484
True if a change was applied to a resource (i.e. it will be False if modifier_func suggested no
483485
change was necessary by returning False).
@@ -488,7 +490,7 @@ def modify_and_apply(self, modifier_func, retries=2, cmd_args=None):
488490
applied_change = False
489491
for attempt in reversed(list(range(retries + 1))):
490492

491-
do_apply = modifier_func(self)
493+
do_apply = modifier_func(self, **kwargs)
492494

493495
# Modifier does not want to modify this object -- stop retrying. Retuning None should continue attempts.
494496
if do_apply is False:

0 commit comments

Comments
 (0)