Giter Club home page Giter Club logo

pyracf's Introduction

pyracf

PyRACF: A Python module for analyzing RACF security

PyRACF is a powerful Python module that simplifies the parsing and querying of any RACF database, providing an efficient and intuitive way to analyze security setups on IBM Z systems. By consuming the IRRDBU00 unload, PyRACF generates "Panda DataFrames" for each 'recordtype', which allow for seamless manipulation and analysis of the data.

Pandas is a powerful data manipulation library in Python that allows for easy querying of the DataFrames generated by PyRACF. With Pandas, you can perform complex queries on the security data to extract meaningful insights into the security posture of your system.

PyRACF's support for saving and loading pickle files makes it easier than ever to work with large RACF datasets, giving you the ability to perform comprehensive analyses of your security setup.

For more information on the various records, please refer to the IBM documentation on IRRDBU00 record types and the record formats produced by the database unload utility. The DataFrames generated by PyRACF feature the same 'fieldnames' as outlined in the documentation, ensuring consistency and accuracy in your analyses.

To get started with PyRACF, install it using pip install pyracf or explore the source code on GitHub. Use PyRACF to take control of your security data and protect your IBM Z systems from threats.

Updates

0.8.7 (fixes for pickles, pytest, wiki)

  • grouptree and ownertree are now properties, no longer callables
  • accept * as a literal value in gfilter( )
  • r.connect('SYS1') and r.connect(None,'IBMUSER') return one level index
  • less contentious column name ALL_USER_ACCESS replaces EFFECTIVE_UACC
  • speed up single profile methods
  • Single value selections return dataframe with columns again
  • giveMeProfiles, generic2regex are now 'internal' (_) functions

0.8.5 (fixes for pickles, pytest, wiki)

  • parse_fancycli now creates empty data frames for pickles it could not find
  • index added to data frames from old pickles, not for pickles that already have index fields
  • pytest framework for QA in development cycle, initial tests ensure attributes are the same with all 3 methods to obtain RACF profiles
  • wiki https://github.com/wizardofzos/pyracf/wiki

0.8.3 (tree print format for grouptree and ownertree)

  • msys.grouptree and msys.ownertree are now properties instead of callables print as unix tree or simple format, e.g. print(msys.ownertree) default format looks like unix tree, change with msys.ownertree.setformat('simple') dict structure accessible through .tree attribute
  • .connect('group') and .connect(None,'user') return a (single level) Index with user IDs, resp., groups, connected to the given entity this helps with queries that test group membership
  • add IDSTAR_ACCESS and ALL_USER_ACCESS to .datasets and .generals with, resp., permitted access on ID(*) and the higher value of UACC and IDSTAR_ACCESS.
  • fixed: correlate also builds internal tables from saved pickles

0.8.2 (property for most application segments)

  • application segments for dataset, group and user entities are avaible with entity prefix, e.g., msys.userTSO, msys.datasetDFP, msys.groupOMVS
  • system related application segments from general resources are published without prefix, e.g., msys.STDATA or msys.MFA
  • old properties msys.installdata and msys.userDistributedMappings are replaced by userUSRDATA and userDistributedIdMappings
  • most of these properties are automatically generated

0.8.0 (acl, gfilter, rfilter methods)

  • selection method gfilter (supports RACF generic patterns) and rfilter (for regex patterns) supports as many parameters as there are index columns in the frame
  • reporting method acl, produces frame with access list, may be used on the entity frames or on the access frames
  • internal frames _connectByGroup and _connectByUser, as alternate index on connectData
  • internal frames _grouptreeLines and _ownertreeLines that return all groups up until SYS1 (or upto a user ID)
  • correlate() invoked automatically by parse() and fancycli()

0.7.1 (General instead of Generic)

  • fixed: Generic should be General resource profiles, variables and methods renamed
  • Mapping between IRRDBU00 record types and variables centralized in a dict
  • global constants related to record types removed
  • parsed records internally stored by (decimal) record type, instead of by name
  • add method to retrieve parsed record count for given name
  • _offsets now a RACF class attribute
  • fixed: pickles with a prefix were selected when loading pickles with no prefix
  • fixed: status property crashed when used before parse() method used, math.floor call is now conditional
  • fixed: record type '0260' in offset.json was malformed
  • updated offsets.json from https://www.ibm.com/docs/en/SSLTBW_3.1.0/com.ibm.zos.v3r1.icha300/format.htm etc
  • getOffsets.py to update the json model
  • fixed: RACF documentation contains incorrect record type 05k0
  • all known record types parsed and loaded into DataFrames
  • index columns assigned to all DataFrames, assigned by new correlate() method
  • new method correlate() to increase speed of subsequent data access, used after parse() or loading of pickles
  • new selection methods similar to user() and group(), that work on index fields. when a parameter is given as None or '**', elements matching the other parameters are returned: datasetPermit and datasetConditionalPermit, with parameters profile(), id() and access() generalPermit and generalConditionalPermit, with parameters resclass(), profile(), id() and access() connect with parameters group() and user()
  • added GPMEM_AUTH to connectData frame, consolidating all connect info into one line

0.6.4 (Add 0209)

  • Added 0209 recordtype to parser. (userDistributedMapping)

0.6.3 (Add fields)

  • Added missing USBD_LEG_PWDHIST_CT, USBD_XPW_PWDHIST_CT, USBD_PHR_ALG, USBD_LEG_PHRHIST_CT, USBD_XPW_PHRHIST_CT, USBD_ROAUDIT and USBD_MFA_FALLBACK to Users dataframe

0.6.2 (Fix XLSX Creation)

  • With newer versions of XlsxWriter there's no more .save(). Changed to .close()
  • Pinned pandas and XlsxWriter versions in setup.py

0.6.1 (Bug free?)

  • XLS generation fully functional again (also for z/VM unloads)
  • Oprhan detection working again
  • Conditional Dataset Access Records now parsing correctly
  • Conditional Dataset Access now correctly pickled :)
  • Fixed parsing of GRCACC records (had misparsed AUTH_ID)
  • Conditional Generic (General) Records now with correct column name (GRCACC_CLASS_NAME)

0.5.4 (Even more recordtypes!!)

  • new property: genericConditionalAccess. Will show GRCACC records.
  • Fixed some nasty 'default recordtypes' bugs

0.5.0 (Pickle FTW!)

  • new function: save_pickles(path=path, prefix=prefix). Will save all parsed dataframes as pickles (/path/prefixRECORDTYPE.pickle)
  • Can now initialize RACF object from pickle-folder/prefix. To reuse earlier saves pickle files. See examples below
  • parse_fancycli now has two optional arguments (save_pickles and prefix) to also save pickle files after parsing to the directory as specified in save_pickles. The prefix argument is only useed with save_pickles isn't False

0.4.5 (Fix Community Update Bug, thanks @Martydog)

  • Group Connections now actually usable :)

0.4.4

  • Internal constants for all recordtypes
  • Improved 'parse_fancycli()'

0.4.3 (Community Update, thanks @Martydog)

  • Add User Group Connections record 203
  • Add User Installation Data record 204

0.4.2

  • Now XLS generation has more checks (fails gracefully if not all required records parsed, works when only genericAccess parsed)
  • Same for Orphan detection
  • Recordtype 0503 (General Resource Members/genericMembers) added

Parsing IRRDBU00 unloads like a boss

>>> from pyracf import RACF
>>> mysys = RACF('/path/to/irrdbu00')
>>> mysys.parse()
>>> mysys.status
{'status': 'Still parsing your unload', 'lines-read': 200392, 'lines-parsed': 197269, 'lines-per-second': 63934, 'parse-time': 'n.a.'}
>>> mysys.status
{'status': 'Ready', 'lines-read': 7137540, 'lines-parsed': 2248149, 'lines-per-second': 145048, 'parse-time': 49.207921}

Using Pickle Files

>>> from pyracf import RACF
>>> mysys = RACF('/path/to/irrdbu00')
>>> mysys.parse_fancycli(save_pickles='/tmp/pickles', prefix='mysys-')
>>> hash(mysys.groups.values.tobytes())
-8566685915584060910

Then later, you don't need to parse the same unload again, just do:

>>> from pyracf import RACF
>>> mysys = RACF(pickles='/tmp/pickles', prefix='mysys-')
>>> hash(mysys.groups.values.tobytes())
-8566685915584060910

All functions

Function/Property Explanation Example
acl Returns DataFrame with access control list for the given frame msys.datasets.acl(permits=True, explode=False, resolve=False, admin=False, access=None, allows=None, sort="profile")
auditors Returns DataFrame with all user having the auditor bit switched on mysys.auditors
connect Returns DataFrame with selected user to group connects mysys.connect('SYS1',None) or mysys.connect('**','IBMUSER')
connects Returns DataFrame with all user to group connects, use connect or connectData instead mysys.connects
connectData Returns DataFrame with all user to group connect information mysys.connectData
correlate assigns index columns and prepares data structures for faster reporting mysys.correlate()
datasetAccess Returns DataFrame with all Accesslists for all dataset profiles mysys.datasetsAccess
dataset Returns DataFrame with selected datasetprofiles mysys.dataset('SYS1.**')
datasetPermit Returns DataFrame with selected permits on datasetprofiles mysys.datasetPermit(profile=, id=, access=)
datasetConditionalPermit Returns DataFrame with selected "PERMIT WHEN()" on datasetprofiles mysys.datasetConditionalPermit(profile=, id=, access=)
datasets Returns DataFrame with all datasetprofiles mysys.datasets
generalAccess Returns DataFrame with with all accesslists for general resource profiles mysys.generalAccess
generalConditionalAccess Returns DataFrame with with all conditional accesslists for general resource profiles mysys.generalConditionalAccess
general Returns DataFrame with selected general resource profiles mysys.general(reclass=, profile=)
generalPermit Returns DataFrame with selected permits on resource profiles mysys.generalPermit(resclass=, profile=, id=, access=)
generalConditionalPermit Returns DataFrame with selected "PERMIT WHEN()" on resource profiles mysys.generalConditionalPermit(resclass=, profile=, id=, access=)
generals Returns DataFrame with with all general resource profiles mysys.generals
getdatasetrisk Returns dict with users that have access or administrative authority on a profile mysys.getdatasetrisk('SYS1.**')
gfilter Returns DataFrame with records matching the index fields specified, using RACF generic patterns mysys.datasets.gfilter('SYS%.')) or mysys.generals.gfilter('FACI*','BPX.'))
group Returns DataFrame with group profiles matching selection mysys.group('SYS1')
groupConnect Returns DataFrame with with user group connection records (0203 recordtype), use connect or connectData instead mysys.groupConnect
groups Returns DataFrame with all group data mysys.groups
groupsWithoutUsers Returns DataFrame with groups that have no connected users mysys.groupsWithoutUsers
grouptree Returns dict with groups arranged by superior group mysys.grouptree()
operations Returns a DataFrame with all operations users mysys.operations
orphans Returns 2 DataFrames one with orphans in dataset profile access lists, and one for general resources d, g = mysys.orphans
ownertree Returns dict with groups arranged by owner group or user ID mysys.ownertree()
parse parses the unload. optional specify recordtypes mysys.parse(recordtypes=['0200'])
parse_fancycli parses the unload with a fancy cli status update. optional recordtypes can be specified mysys.parse_fancycli(recorddtypes=['0200'])
revoked Returns a DataFrame with all revoked users mysys.revoked
rfilter Returns DataFrame with records matching the index fields specified, using regex patterns mysys.generals.rfilter('FAC.','BPX..'))
save_pickles Saves all parsed types as pickle files mysys.save_pickles(path='/tmp', prefix='mysys-')
specials Returns a DataFrame with all special users mysys.specials
status Returns JSON with parsing status mysys.status
uacc_read_datasets Returns a DataFrame with all dataset profiles having UACC=READ mysys.uacc_read_datasets
uacc_update_datasets Returns a DataFrame with all dataset profiles having UACC=UPDATE mysys.uacc_update_datasets
user Returns DataFrame with with user profiles matching selection mysys.user('IBMUSER')
users Returns DataFrame with all user base data mysys.users
xls Creates an XLSX with all permits per class mysys.xls(fileName='myxls.xlsx')

Example use-case

Get all users that have not logged in (on?) since January 1st 2022. And print userID and last logon...

import time
from pyracf import IRRDBU

mysys = IRRDBU('/path/to/irrdbu00')
mysys.parse()
while mysys.status['status'] != 'Ready':
    time.sleep(5)
selection = mysys.users.loc[mysys.users.USBD_LASTJOB_DATE<="2022-01-01"][['USBD_NAME','USBD_LASTJOB_DATE']]
for user in selection.values:
  print(f"Userid {user[0]}, last active: {user[1]}")

Create a neat XLSX

import time
from pyracf import IRRDBU
mysys = IRRDBU('/path/to/irrdbu00')
mysys.parse()
while mysys.status['status'] != 'Ready':
    time.sleep(5)
mysys.xls('/path/to/my.xlsx')

Print z/OS UNIX profiles

mysys.general('FACILITY', 'BPX.SUPERUSER')
mysys.general('FACILITY', 'BPX.**')  # show only the FACILITY BPX.** profile
mysys.general('FACILITY')  # show all FACILITY profiles

mysys.generals.gfilter('FAC*', 'BPX.**')  # show all BPX profiles
mysys.generals.gfilter('UNIXPRIV') # print all in UNIXPRIV

Show group information

mysys.connect('SYS1')            # users connected to SYS1 groups
mysys.connect('**','IBMUSER')    # groups IBMUSER is connected to

mysys.connectData.gfilter('SYS*','IBM*') # connects using patterns
mysys.connectData.rfilter('SYS[AB].*','PROD.*') # regex with alternatives
mysys.connectData.query("USCON_GRP_SPECIAL=='YES' & USCON_REVOKE=='YES'")  # group special revoked
mysys.connectData.query("GPMEM_AUTH==['CONNECT','JOIN']")  # users with connect authorities

mysys.users.query("index in @mysys.connect('SYS1').index")  # user details of users connected to SYS1

Show access list information

mysys.datasetPermit('SYS1.**')    # IDs permitted on SYS1.**
mysys.datasetPermit(id='IBMUSER', access='ALTER')    # where is IBMUSER permitted
mysys.datasetPermit(id='*')       # where is ID(*) permitted

mysys.datasets.gfilter('SYS1.**').acl(access='ALTER')    # IDs ALTER access on any SYS1 dataset profile
mysys.datasets.gfilter('SYS%.**').acl(allows='UPDATE')    # IDs with access that allows UPDATE
mysys.datasets.gfilter('SYS%.**').acl(allows='UPDATE', resolve=True)    # groups and user permits combined 
mysys.datasets.gfilter('PROD.**').acl(permits=False, admin=True)    # who can change groups or profiles to change access on PROD data sets
mysys.generals.gfilter('XFAC*', 'CKR.**').acl() # permits on zSecure Admin/Audit profile

mysys.datasets.query("ALL_USER_ACCESS=='UPDATE'")    # UACC or ID(*) set to UPDATE

Show group tree information

print(msys.grouptree)  # group - superior group - subgroups in UNIX tree format
print(msys.ownertree.setformat('simple'))  # group - OWNER in simple format
msys.grouptree.tree  # get the dict

Updates

In this version we introduced IRRRDBU as an alternative to RACF. Examples have been updated. The RACF class from previous version is still available, but you're advised to change this to IRRDBU, as future version will have another user of the RACF class.

Contribute to PyRACF

If you've some additions and/or bugfixes, feel free to fork this repository, make your additions and fire a pull request.

pyracf's People

Contributors

lnlyssg avatar martydog avatar rob-vh avatar wizardofzos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

pyracf's Issues

different ways to access the grouptree/ownertree

When you issue r.ownertree in ipython, you acually get r.ownertree.__repr__(). This prints {} because

  • the tree object is a subclass of dict,
  • the (apparent) default dict objects is not filled in, but instead the .tree attribute is
  • repr does not exist
    When you issue print(r.ownertree), you invoke the r.ownertree.__lst__() and this pretty-prints the tree
    If you need the tree dict object, documentation states you should use r.ownertree.tree

Should ownertree and grouptree not be '@properties'?

No args in function?

Also this part in ownertree

return self._ownertree if self._ownertree else self.tree("GPBD_OWNER_ID")

else part yields

>>> r.tree("GBPD_OWNER_ID")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/henri/pyracf/lib/python3.8/site-packages/pyracf/__init__.py", line 1016, in tree
    higher_ups = self._groups.groupby(linkup_field)
  File "/home/henri/pyracf/lib/python3.8/site-packages/pandas/core/frame.py", line 8252, in groupby
    return DataFrameGroupBy(
  File "/home/henri/pyracf/lib/python3.8/site-packages/pandas/core/groupby/groupby.py", line 931, in __init__
    grouper, exclusions, obj = get_grouper(
  File "/home/henri/pyracf/lib/python3.8/site-packages/pandas/core/groupby/grouper.py", line 985, in get_grouper
    raise KeyError(gpr)
KeyError: 'GBPD_OWNER_ID'

when loading from pickles?

NameError: name 'DSACC_RECORDTYPE' is not defined

Using this code:

import time
from pyracf import IRRDBU
mysys = IRRDBU('/x/IRRDBU00.txt')
mysys.parse()
while mysys.status['status'] != 'Ready':
    time.sleep(5)
mysys.xls('/x/y/my.xlsx')

I'm getting the below error:

Traceback (most recent call last):
  File "/x/x/test.py", line 7, in <module>
    mysys.xls('/x/y/my.xlsx')
  File "/x/y/opt/miniconda3/lib/python3.9/site-packages/pyracf/__init__.py", line 548, in xls
    if self._records[DSACC_RECORDTYPE]['parsed'] + self._records[GRACC_RECORDTYPE]['parsed'] == 0:
NameError: name 'DSACC_RECORDTYPE' is not defined

idea: convert from yaml to strictYAML

yaml aggressively converts str values to int, float or bool, so you have to quote words like YES and NO, or they'll be booled.
also, yaml quietly allows duplicate keys (and keeps the last) in dict definitions.

StrictYAML does not do automatic (re-)typing, and has a built-in syntax validator.
You have to jump through some hoops to allow flow style specification ( [DSBD,GRBD] or [NONE,READ] instead of their multi-line equivalent).

pros and cons pypi

So far, this applies to RuleVerifier, but could have more use.

.acl() usage

When using the .acl(...) suffix method sometimes the 'column names' get stripped of their prefix.
It's workable, but would be more consitent is (for instance) this

>>> r.datasetAccess.gfilter('SYS1.S*.**').acl()
                  NAME VOL  USER_ID  AUTH_ID  ACCESS
0   SYS1.SECRET.*.**        USER1  USER1     ALTER

Would be

>>> r.datasetAccess.gfilter('SYS1.S*.**').acl()
            USBD_NAME VOL  USER_ID  AUTH_ID  ACCESS
0   SYS1.SECRET.*.**        USER1  USER1   ALTER
```

idea: intelligent selection or positioning of columns in ProfileFrame

ProfileFrames show all columns in the Frame, plus the index fields. This may result in useful information being invisible (caught in the ellipsis), such as DSBD_UACC, and not so informative fields, like the recordtype taking prominent place.
The default view of a Frame should be useful, we should not claim that "the user can rearrange things if they want."

parse fails when input unload file has garbage at the end

unload file has x'001a' (CTRL-Z) at the end after file transfer, and parse_t() tries to find the layout of the record for record type x'001a'. This results in KeyError, obviously:

File "/pyracf-dev-0.9/src/pyracf/__init__.py", line 324, in parse_t
    offsets = RACF._recordtype_info[r]["offsets"]
KeyError: '\x1a'

We need to check if r in RACF._recordtype_info, or add a try:

split capabilities in separate packages

Currently, everything is shipped in package pyracf.

Core irrdbu00 reader is in pyracf/__init__.py, which defines the RACF class. The RACF class has methods:

  • coded as functions in __init__.py
  • defined through property() as class properties, this includes most of the publisher names
  • inherited from ProfilePublisher, XlsWriter

RACF instances create dfs:

  • dataframes (starting with _) are defined as ProfileFrame() and then linked using setattr

It is easy to delete XlsWriter from the RACF class, and force users to import the module containing XlsWriter.
The same applies to RuleVerifier, i.e. create verifier instances explitly using v = RuleVerifier(r), but where should we store the rule_verifier.py module?

Also, RuleVerifier needs some of the same classes that RACF uses. That implies creating a separate directory with class definitions, that modules in both pyracf and verifier directories import from.

Same could be done with the ProfilePublisher class (optional), and the FrameFilter class (also shared between pyracf and verifier).

Do we want multiple packages?

minimal columns on .acl( ) or generate all columns even if they are empty by definition?

.acl( ) provides support for normal ACL and conditional ACL, and even for combined output.

Many customers will object if CATYPE, CANAME, and their 2 less known siblings are shown on all reports: "what is this, I have not asked for it." So currently, if there is no record in the output with CATYPE filled in, the 4 columns are dropped.

Programmatic users that integrate output of .acl( ) with other data now have to include trivial logic to deal with the conditional conditional columns:

# combine r.datasets, r.datasetAccess and r.datasetConditionalAccess
access = r.datasets.acl()
if 'CATYPE' in access.columns:
  # CATYPE is there
else:
  # only normal ACL

Is there a need to always add the 4 columns, for these automated data slurpers? Say, add an option "all_columns=True" (with default = False)?

UID and GID values are fixed length, 10 character, with leading 0, making it harder to read/use the values

The unload file has UID values stored as fixed length, leading zero, numbers:

_NAME
ADCDA      0000990008
ADCDB      0000990009
ADCDMST    0000000000
ASH        0000000000
BILLA      0000000100
              ...    
ZOSCAGL    0000009081
ZOSCSRV    0000009082
ZOSMFAD    0000990007
ZOSUGST    0000009080
9735       0000000100

This makes it harder to get a list of all users with UID 0, because you have to be careful count the number of characters.

r.userOMVS.loc[r.userOMVS.USOMVS_UID=='0000000000']
vs
r.userOMVS.loc[r.userOMVS.USOMVS_UID=='0']

We could strip the leading zeros in correlate(). Objections?

change name of selection methods?

Currently we have .gfilter() and .rfilter(), they implement (positive) selection. We originally decided on gfilter (generic filter) as the functionality we needed is similar to the SEARCH FILTER( ) command in TSO, and .select .match and .filter were already taken by Pandas.
Looking more closely at the built-in .filter method, it does something similar although it does not directly allow generic filters:

>>> r.users.filter(['ADCDA','IBMUSER'],axis=0)
 	USBD_RECORD_TYPE 	USBD_NAME 	USBD_CREATE_DATE 	USBD_OWNER_ID 	USBD_ADSP 	USBD_SPECIAL 	USBD_OPER 	USBD_REVOKE 	USBD_GRPACC 	USBD_PWD_INTERVAL 	... 	USBD_PPHENV_EXISTS 	USBD_PWD_ALG 	USBD_LEG_PWDHIST_CT 	USBD_XPW_PWDHIST_CT 	USBD_PHR_ALG 	USBD_LEG_PHRHIST_CT 	USBD_XPW_PHRHIST_CT 	USBD_ROAUDIT 	USBD_MFA_FALLBACK 	USBD_PHR_INTERVAL
_NAME 																					
ADCDA 	0200 	ADCDA 	2007-06-01 	IBMUSER 	NO 	NO 	NO 	YES 	NO 	180 	... 	NO 	LEGACY 	000 	000 	NOPHRASE 	000 	000 	NO 	NO 	00000
IBMUSER 	0200 	IBMUSER 	1995-06-06 	IBMUSER 	NO 	YES 	YES 	YES 	NO 	000 	... 	NO 	LEGACY 	000 	000 	NOPHRASE 	000 	000 	NO 	NO 	00000

2 rows × 52 columns

>>> t.users.filter(regex='IBM.*',axis=0)
 	USBD_RECORD_TYPE 	USBD_NAME 	USBD_CREATE_DATE 	USBD_OWNER_ID 	USBD_ADSP 	USBD_SPECIAL 	USBD_OPER 	USBD_REVOKE 	USBD_GRPACC 	USBD_PWD_INTERVAL 	... 	USBD_PPHENV_EXISTS 	USBD_PWD_ALG 	USBD_LEG_PWDHIST_CT 	USBD_XPW_PWDHIST_CT 	USBD_PHR_ALG 	USBD_LEG_PHRHIST_CT 	USBD_XPW_PHRHIST_CT 	USBD_ROAUDIT 	USBD_MFA_FALLBACK 	USBD_PHR_INTERVAL
_NAME 																					
IBMUSCN 	0200 	IBMUSCN 	2019-11-11 	IBMUSER 	NO 	NO 	NO 	NO 	NO 	180 	... 	NO 	NOPASSWORD 	000 	000 	NOPHRASE 	000 	000 	NO 	NO 	00000
IBMUSECN 	0200 	IBMUSECN 	2019-11-11 	IBMUSER 	NO 	NO 	NO 	NO 	NO 	180 	... 	NO 	NOPASSWORD 	000 	000 	NOPHRASE 	000 	000 	NO 	NO 	00000
IBMUSER 	0200 	IBMUSER 	1995-06-06 	IBMUSER 	NO 	YES 	YES 	YES 	NO 	000 	... 	NO 	LEGACY 	000 	000 	NOPHRASE 	000 	000 	NO 	NO 	00000

3 rows × 52 columns

There are 3 issues I would like to address:

  1. we now have gfilter() and rfilter(), after 1 week not using pyracf, I have to think hard what the method name is, again. r.users.select('IBM*')..... nope. We chose gfilter because the name was available, well, a strange looking function name is not always the right choice
  2. someone mentioned rfilter reminded him of "RACF filters" and gfilter didn't remind him of generic patterns.
  3. it would be great to be able to SELECT and also EXCLUDE records, like so:
    r.users.gfilter('IBM*').gfilter('NOT', 'IBMUSER')
    or
    r.users.gfilter('IBM*').gfilter('IBMUSER' ,exclude=True)
    so we can show all users starting with IBM, except IBMUSER.

I wonder, should be drop gfilter and rfilter, and take a different verb?

a. take it or leave it:
r.users.take('IBM*').leave('IBMUSER')
b. leave feels like "leave it in" and "skip" is the opposite of include:
r.users.take('IBM*').skip('IBMUSER')
but also
r.users.skip('IBMUSER')

The straight-forward use of take and skip should focus on RACF idiom, so it uses generic patterns. More advanced users might write regex filters, but they would be familiar with Python's r-string type (to take the backslash at face value).
But there is no way for the function to check if the caller entered an r prefix for the string. And we do not want to force the more advanced user to always start their regex pattern with a ^.
So what it we add an 'r' keyword in these methods that activates regex, like so:

r.users.take('r', 'IBM.*').skip('IBMUSER')

or more intuitively for regex connoisseurs force them to enter the program object or package name (basically anything that is not a str:

r.datasets.take(re, r'SYS\d\..*').skip(re, r'SYS1\.\*\*')

r.generals.take(re, 'FACILITY', r'BPX\..*')

forget about Wiki?

There are too many deficiencies with gollum (github's version of Wiki), in combination with sphinx.

  • sphinx does not generate a _Sidebar.md, you have to rename the index.md into _Sidebar.md
  • gollum doesn't accept .md filetypes in URIs, you have to add a sphinx global option to kill those in the generated md flies
  • gollum does not respect directories (folders) in the URI of links, so the source directory with autodoc files results in 404 errors from the _Sidebar, meaning, you would have to edit _Sidebar.md anyway
  • there is only 1 Wiki link that is branch independent
  • you have to maintain a separate git and do a separate push to update the Wiki
  • a summary

Advantage of Wiki? Ehrm, it displays all of the docs contents, where github's interpretation of rst files is lacking (doesn't support table from file, doesn't interpret the autodoc directives).

So, lets

  1. rename the _build directory to build,
  2. build md files after any change using make markdown,
  3. git add docs/build/markdown/* (including ./source)
  4. put a link to docs/build/markdown/index.md into README.md
  5. deactivate Wiki
  6. Bob's your uncle

This works, except...

  1. Make sure the .md filetype is present in the URI of all links
  2. selecting the link from README, you are dumped into Preview mode, with the navigator as a sidebar, and no fancy sidebar.
  3. somehow, the one single screenshot in Installation results in 404. That's because Installation.md looks for _static/file.png, whereas the file really is in ../_static/file.png

Still, it does provide a manual for the working branches.

Records not in file -> errors

When requesting a record type that's NOT in the unload, parse_fancycli fails.... as the 'key' is not in the stats..._records thinggy

Filtered parse breaks _correlate

Looks like we cannot always 'just' activate the _correlate.. as there can be required other types. For instance, running a parse on users and groups only yields the following:

r2.parse(recordtypes=['0100','0200'])
True
>>> Exception in thread Thread-2 (parse_t):
Traceback (most recent call last):
  File "/usr/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.10/threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)
  File "/home/henri/repos/pyracf/venv/lib/python3.10/site-packages/pyracf/__init__.py", line 339, in parse_t
    self._correlate()
  File "/home/henri/repos/pyracf/venv/lib/python3.10/site-packages/pyracf/__init__.py", line 379, in _correlate
    raise StoopidException("Need to parse GPMEM and USCON first...")
pyracf.StoopidException: Need to parse GPMEM and USCON first...

Maybe not run _correlate if recordtypes is specified?
Maybe parse required records regardless of what user specified?
Maybe even say goodbye to the selection of what records to parse, as parsing is lightning-fast now anyway?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.