Package: keystone-light
keystone-light implements a Python interface to a very limited subset of
theOpenStack Identity API v3.Initial goal:access to OpenStack Swift, using the Identity API v3, but
with a lot fewer dependencies.As of this writing, thepython-keystoneclientrequireskeystoneauth1andoslo.*, which in turn require some more. We
only require theubiquitousrequests(andPyYAML), which you
generally already have installed anyway.Example usage#!/usr/bin/env python3fromurllib.parseimporturljoinimportrequestsfromkeystone_lightimportCloud,CloudsYamlConfig,PermissionDenieddefget_projects(cloud):"Yields projects, sorted by domain and project name"domains=cloud.get_domains()fordomaininsorted(domains,key=(lambdax:x.name)):ifdomain.name=='Default':# print('WARN: skipping domain Default (fixme?)')continueprojects=domain.get_projects()forprojectinsorted(projects,key=(lambdax:x.name)):project.domain=domainyieldprojectdef_give_us_project_perms_through_admin_group(project):"""
    Make sure we are in the *-admin group. Make sure the *-admin
    group has permissions on the project.
    """cloud=project.clouddom_admin_group=project.domain.get_admin_group()# First check if we're member of the group at all.token=cloud.get_system_token()auth_headers={'X-Auth-Token':str(token)}try:# FIXME: Undocumented access to system_token!user_id=token.data['user']['id']assertuser_idandisinstance(user_id,str),user_idexceptKeyError:raiseValueError('missing user.id?',token.data)# Are we in the *-admin group?url=urljoin(cloud.base_url,'/v3/groups/{group_id}/users/{user_id}'.format(group_id=dom_admin_group.id,user_id=user_id))out=requests.head(url,headers=auth_headers)ifout.status_code==404:# Add us to the group.out=requests.put(url,headers=auth_headers)assertout.status_code==204,('PUT',url,out.status_code,out.text)# Double check.out=requests.head(url,headers=auth_headers)assertout.status_code==204,('HEAD',url,out.status_code,out.text)# Grant *-admin power to the project.admin_role=cloud.get_role(name='admin')# or 'reader'url=urljoin(cloud.base_url,'/v3/projects/{project_id}/groups/{group_id}/roles/{role_id}'.format(project_id=project.id,group_id=dom_admin_group.id,role_id=admin_role.id))out=requests.put(url,headers=auth_headers)assertout.status_codein(201,204),('PUT',url,out.status_code,out.text)defget_swift_stat_ensuring_permissions(project):"Get Swift v1 stat on a project (previously: tenant)"try:stat=project.get_swift().get_stat()exceptPermissionDenied:# We don't have permission to access the project? Upgrade the# permissions and try again._give_us_project_perms_through_admin_group(project)else:returnstat# Try again. Should succeed now, with the added permissions.try:stat=project.get_swift().get_stat()exceptPermissionDeniedase:raiseMyPermissionDenied('EPERM on{domain}.{project}:{exc}{exc_args}'.format(domain=project.domain.name,project=project.name,exc=e.__class__.__name__,exc_args=e.args))fromeelse:returnstat# Take config from ~/.config/openstack/clouds.yaml and select# 'my-cloud-admin', like the openstack(1) --os-cloud option.config=CloudsYamlConfig('my-cloud-admin')cloud=Cloud(config)forprojectinget_projects(cloud):swift_stat=get_swift_stat_ensuring_permissions(project)print('{:15s}{:23s}{:21d}B ({}objects,{}containers)'.format(project.domain.name[0:15],project.name,int(swift_stat['X-Account-Bytes-Used']),swift_stat['X-Account-Object-Count'],swift_stat['X-Account-Container-Count']))Example output$python3example.pydomainx         project                  3489 B (2 objects, 1 containers)
domainx         otherproject       1455042022 B (267 objects, 1 containers)
...Swift Example usagefromkeystone_lightimportCloud,DirectConfigKEYSTONE_URL='https://<DOMAIN>:<USER>:<PASS>@KEYSTONE'SWIFT_PROJECT='<DOMAIN>:<PROJECT>'SWIFT_CONTAINER='some-container'config=DirectConfig(KEYSTONE_URI)project=Cloud(config).get_current_project()assertproject.get_fullname()==SWIFT_PROJECT,project.get_fullname()swift=project.get_swift()container=swift.get_container(SWIFT_CONTAINER)# (Re-)upload file:filename=('bloblet.bin'ifFalseelse'blobzilla.bin')withopen(filename,'rb')asfp:try:container.delete(filename)exceptFileNotFoundError:pass# TIP: Use ChunkIteratorIOBaseWrapper(fp) if the input file# is a pipe/stream.container.put(filename,fp)# Download file:filename2='{}.retrieved'.format(filename)withcontainer.get(filename)asresponse,\open(filename2,'wb')asfp:forchunkinresponse.iter_content(chunk_size=8192):fp.write(chunk)# Check and compare:withopen(filename,'rb')asfp,\open(filename2,'rb')asfp2:buf=buf2=Truewhilebufandbuf2:buf=fp.read(8192)buf2=fp2.read(8192)assertbuf==buf2assertbuf==buf2And an example with timing:fromtimeitimporttimeit# ...# Download file:filename2='{}.retrieved'.format(filename)def_get():withcontainer.get(filename)asresponse,\open(filename2,'wb')asfp:forchunkinresponse.iter_content(chunk_size=8192):fp.write(chunk)print('{:7.3f}GET'.format(timeit(number=1,stmt=_get)))