Skip to content

Commit 7a1aa42

Browse files
committed
rm configyuration loading can no wbe incremental - load until the config is found - and oslcquery with -V npow shows a curl equivalent (not very tested!)
1 parent 8f30f97 commit 7a1aa42

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+16486
-16336
lines changed

elmclient/_rm.py

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ def __init__(self, name, project_uri, app, is_optin=False, singlemode=False,defa
8484
self.default_query_resource = "oslc_rm:Requirement"
8585
self._iscomponent=False
8686
self._confs_to_load = []
87-
87+
self._confstoparent = []
88+
8889
# save a folder details, and return the new folder instance
8990
def _savefolder( self, parent, fname, folderuri ):
9091
ROOTNAME = "+-+root+-+"
@@ -511,13 +512,17 @@ def add_external_component(self,compu):
511512
self._components[compu]['component'] = c
512513
return c
513514

514-
def load_configs(self, cacheable=True ):
515-
logger.debug( f"Loading configs {self._confs_to_load=}" )
515+
# if given stopatnameoruri returns True if found, False if not (which means all configs have been loaded and it isn't there!)
516+
def load_configs(self, cacheable=True, stopatnameoruri=None, verbose=False, incremental=False ):
517+
logger.debug( f"Loading configs {self._confs_to_load=}, {stopatnameoruri=}" )
516518
# load configurations
517519
# and build a tree with initial baseline as root, alternating baseline and stream nodes each with a list of children, so it can be walked if needed
518520
self.configTree = anytree.AnyNode(name='theroot',title='root', created=None, typesystem=None, ismutable=False, ischangeset=False )
519-
confstoparent = []
521+
result = False
522+
520523
while True:
524+
if verbose:
525+
print( ".",end="" )
521526
if not self._confs_to_load:
522527
break
523528
confu = self._confs_to_load.pop()
@@ -546,6 +551,8 @@ def load_configs(self, cacheable=True ):
546551
confmembers_x = list( set( rdfxml.xml_find_elements(configs_xml, './/oslc_config:Configuration') + rdfxml.xml_find_elements(configs_xml, './/oslc_config:Stream') + rdfxml.xml_find_elements(configs_xml, './/oslc_config:Baseline') + rdfxml.xml_find_elements(configs_xml, './/oslc_config:ChangeSet') ) )
547552

548553
for confmember_x in confmembers_x:
554+
if verbose:
555+
print( ">",end="",flush=True )
549556
# print( f"========================\n{confmember_x=}" )
550557
# print( f"{confmember_x.tag=}" )
551558
# print( "XML=",ET.tostring( confmember_x ) )
@@ -563,6 +570,10 @@ def load_configs(self, cacheable=True ):
563570
ismutable = False
564571
ischangeset = False
565572
conftitle = rdfxml.xmlrdf_get_resource_text(confmember_x, './dcterms:title')
573+
574+
if stopatnameoruri and ( conftitle==stopatnameoruri or thisconfu==stopatnameoruri ):
575+
result = True
576+
566577
created = rdfxml.xmlrdf_get_resource_uri(confmember_x, './dcterms:created', exceptionifnotfound=True)
567578
# print( f"{conftitle=}" )
568579
# print( f"{created=}" )
@@ -620,18 +631,21 @@ def load_configs(self, cacheable=True ):
620631
thisnode = anytree.AnyNode( None, name=thisconfu, title=conftitle, conftype=conftype, created=created, typesystem=_newtypesystem.TypeSystem(conftitle, thisconfu), ismutable=ismutable ) #TypeSystem(conftitle, thisconfu), ismutable=ismutable, ischangeset=ischangeset )
621632
if parentnode is None:
622633
# do this one later
623-
confstoparent.append( ( thisnode, theparent_u ) )
634+
self._confstoparent.append( ( thisnode, theparent_u ) )
624635
# print( f"Saved for later {confstoparent[-1]=}" )
625636
else:
626637
# parent is known so attach to it
627638
thisnode.parent = parentnode
628639
# print( f"Config {conftitle} Added config {thisconfu} parent={parentnode}" )
640+
# now check if stopatnameoururi is present and if so set result True
641+
if result:
642+
break
629643

630644
# now iterate over the unparented nodes repeatedly finding their parents until there are none left because all have been parented
631-
while confstoparent:
645+
while self._confstoparent:
632646
# we're going to copy unparented node into this new list, then copy the new list into confstoparent
633647
newconfstoparent = []
634-
for (i,nodedetails) in enumerate(confstoparent):
648+
for (i,nodedetails) in enumerate(self._confstoparent):
635649
(node,theparent_u) = nodedetails
636650
# if we find the parent, attach it - if not found, add it to this list of those still needing parent
637651
parentnode = anytree.search.find( self.configTree, filter_=lambda n: n.name==theparent_u )
@@ -640,10 +654,14 @@ def load_configs(self, cacheable=True ):
640654
thisnode.parent = parentnode
641655
# print( f"Parented {node}" )
642656
else:
643-
# remember this still needs parenting
644-
newconfstoparent.append( (node,parent) )
645-
confstoparent = newconfstoparent
646-
657+
# remember this still needs parenting! Will be found on a later pass
658+
newconfstoparent.append( (node,theparent_u) )
659+
self._confstoparent = newconfstoparent
660+
661+
if verbose:
662+
print( "" )
663+
664+
return result
647665

648666
def load_configtree( self, *, fromconfig_u=None, loadbaselines=False, followsubstreams=False, loadchangesets=False, alwayscaching=False ):
649667
# show the config tree
@@ -703,7 +721,7 @@ def load_configtree( self, *, fromconfig_u=None, loadbaselines=False, followsubs
703721
raise Exception( f"Unkown type {typedetails[1]}" )
704722

705723

706-
def get_local_config(self, name_or_uri, global_config_uri=None):
724+
def get_local_config(self, name_or_uri, global_config_uri=None, verbose=False, incremental=False ):
707725
logger.info( f"GLC {self=} {name_or_uri=}" )
708726
# print( f"GLC {self=} {name_or_uri=} {global_config_uri=}" )
709727
if global_config_uri:
@@ -719,7 +737,6 @@ def get_local_config(self, name_or_uri, global_config_uri=None):
719737
raise Exception( 'Cannot find configuration [%s] in project [%s]' % (name_or_uri, self.uri))
720738
result = config_uri
721739
else:
722-
self.load_configs()
723740
result = None
724741
filter = None
725742
if name_or_uri.startswith("S:"):
@@ -731,14 +748,19 @@ def get_local_config(self, name_or_uri, global_config_uri=None):
731748
elif name_or_uri.startswith("C:"):
732749
filter="ChangeSet"
733750
name_or_uri = name_or_uri[2:]
734-
for cu, cd in self._configurations.items():
735-
# print( f"{cu=} {cd=}" )
736-
if filter and cd['conftype'] != filter:
737-
continue
738-
if cu == name_or_uri or cd['name'] == name_or_uri:
739-
if result:
740-
raise Exception( f"Config {name_or_uri} isn't unique - you could try prefixing it with S: for stream, B: for baseline, or C: for changeset")
741-
result = cu
751+
752+
while result is None:
753+
for cu, cd in self._configurations.items():
754+
# print( f"{cu=} {cd=}" )
755+
if filter and cd['conftype'] != filter:
756+
continue
757+
if cu == name_or_uri or cd['name'] == name_or_uri:
758+
if result:
759+
raise Exception( f"Config {name_or_uri} isn't unique - you could try prefixing it with S: for stream, B: for baseline, or C: for changeset")
760+
result = cu
761+
if result is None:
762+
if not self.load_configs( stopatnameoruri=name_or_uri, verbose=verbose, incremental=incremental ):
763+
break
742764
# print( f"GLC {result} {self=} {name_or_uri=}" )
743765

744766
return result

elmclient/examples/oslcquery.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
# SPDX-License-Identifier: MIT
44
##
55

6+
#
7+
# app to exercise OSLC Query capabilities in (relatively) human-friendly way - the key thing is to allow user to specify things by name and the app finds the corresponding URI
8+
# See OSLC Query 3.0 standard https://docs.oasis-open-projects.org/oslc-op/query/v3.0/os/oslc-query.html
9+
#
610

711
import argparse
812
import csv

elmclient/httpops.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
import json
1212
import logging
1313
import lxml.etree as ET
14+
import platform
1415
import re
16+
import shlex
1517
import time
1618
import urllib
1719

@@ -22,6 +24,66 @@
2224

2325
logger = logging.getLogger(__name__)
2426

27+
is_windows = any(platform.win32_ver())
28+
29+
30+
def quote( s ):
31+
if is_windows:
32+
for c in s:
33+
# only quote if there's a shell-unfriendly character!
34+
if c not in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._':
35+
return f'"{s}"'
36+
return s
37+
else:
38+
return shlex.quote(s)
39+
40+
# curl login example (non-JAS):
41+
# curl -k -c cookies.txt "https://jazz.ibm.com:9443/jts/authenticated/identity"
42+
# curl -k -L -b cookies.txt -c cookies.txt -d j_username=ibm -d j_password=ibm "https://jazz.ibm.com:9443/jts/authenticated/j_security_check"
43+
44+
# derived from https://github.com/ofw/curlify/blob/master/curlify.py but with quoting fixed so on Windows it generates Windows-friendly curl commands
45+
def to_curl(request, compressed=False, verify=True):
46+
"""
47+
Returns string with curl command by provided request object
48+
49+
Parameters
50+
----------
51+
compressed : bool
52+
If `True` then `--compressed` argument will be added to result
53+
"""
54+
parts = [
55+
('curl', None),
56+
('-X', request.method),
57+
('-b','cookies.txt'),
58+
('-c','cookies.txt'),
59+
]
60+
61+
for k, v in sorted(request.headers.items()):
62+
parts += [('-H', '{0}: {1}'.format(k, v))]
63+
64+
if request.body:
65+
body = request.body
66+
if isinstance(body, bytes):
67+
body = body.decode('utf-8')
68+
parts += [('-d', body)]
69+
70+
if compressed:
71+
parts += [('--compressed', None)]
72+
73+
if not verify:
74+
parts += [('--insecure', None)]
75+
76+
parts += [(None, request.url)]
77+
78+
flat_parts = []
79+
for k, v in parts:
80+
if k:
81+
flat_parts.append(quote(k))
82+
if v:
83+
flat_parts.append(quote(v))
84+
85+
return ' '.join(flat_parts)
86+
2587
# prefix on a password that indicates it's an application password structure
2688
AP_PREFIX ="ap:"
2789

@@ -559,7 +621,7 @@ def get_auth_path(self, request_url, response):
559621
# 1. if the response indicates login is required then login and try the request again
560622
# 2. if request is rejected for various reasons retry with the CSRF header applied
561623
# supports Jazz Form authorization and Jazz Authorization Server login
562-
def _execute_one_request_with_login( self, *, no_error_log=False, close=False, donotlogbody=False, retry_get_after_login=True, remove_headers=None, remove_parameters=None, intent=None, action = None, automaticlogin=True ):
624+
def _execute_one_request_with_login( self, *, no_error_log=False, close=False, donotlogbody=False, retry_get_after_login=True, remove_headers=None, remove_parameters=None, intent=None, action = None, automaticlogin=True, showcurl=False ):
563625
# if intent is None:
564626
# raise Exception( "No intent provided!" )
565627
intent = intent or ""
@@ -575,6 +637,8 @@ def _execute_one_request_with_login( self, *, no_error_log=False, close=False, d
575637
# if Configuration-Context is not None:
576638
# print( f"Copied header Configuration-Context to parameter oslc_config.context" )
577639
request.params['oslc_config.context'] = request.headers['Configuration-Context']
640+
# del request.headers['Configuration-Context']
641+
# print( f"Deleted C-C" )
578642

579643
# ensure keep-alive/close
580644
if close:
@@ -591,6 +655,7 @@ def _execute_one_request_with_login( self, *, no_error_log=False, close=False, d
591655

592656
# this is for generic API debugging to be able to remove any header before it's actually sent!
593657
if remove_headers:
658+
print( f"{remove_headers=}" )
594659
for h in remove_headers:
595660
# print( f"{request.headers=}" )
596661
if h in request.headers:
@@ -602,6 +667,9 @@ def _execute_one_request_with_login( self, *, no_error_log=False, close=False, d
602667
try:
603668
prepped = self._session.prepare_request( request )
604669

670+
if showcurl:
671+
print( f"Curl command (assuming auth cookies are in cookies.txt) is {to_curl(prepped)}" )
672+
605673
# check for us using an appp password for this url (context root) and if so extend the User-Agent header
606674
prepped.headers['User-Agent'] += addhdr
607675

@@ -611,6 +679,8 @@ def _execute_one_request_with_login( self, *, no_error_log=False, close=False, d
611679

612680
response.raise_for_status()
613681

682+
683+
614684
if not automaticlogin:
615685
# print( f"No auto login {response}" )
616686
return response

elmclient/oslcqueryapi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ def _execute_vanilla_oslc_query(self, querycapabilityuri, query_params, orderby=
615615
intent = f"Retrieve {utils.nth(page)} page of OSLC query results"
616616

617617
# request this page
618-
this_result_xml = self.execute_get_rdf_xml(query_url, params=params, headers=headers, cacheable=cacheable, intent=intent)
618+
this_result_xml = self.execute_get_rdf_xml(query_url, params=params, headers=headers, cacheable=cacheable, intent=intent, showcurl=verbose)
619619
queryurls.append(query_url)
620620

621621
if saverawresults:

0 commit comments

Comments
 (0)