Skip to content

Commit

Permalink
cli-eaa 0.6.1 (#31)
Browse files Browse the repository at this point in the history
* Add JSON for directory list and extra fields in the CSV output
* Update example in doc
* Initial stable code to allow pulling directory info/health
* Remove unused attributes, add connectors
* Bump version to 0.6.1
* Set directory follow mode pull interval to 5m
* add test for directory health
  • Loading branch information
bitonio authored Oct 9, 2023
1 parent 3415750 commit 9486cc0
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 27 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.6.0
0.6.1
2 changes: 1 addition & 1 deletion bin/akamai-eaa
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ if __name__ == "__main__":
if hasattr(config, 'directory_id'):
directory_id = config.directory_id
d = DirectoryAPI(config, directory_id)
d.list_directories()
d.list_directories(stop_event=cli.stop_event)
else:
d = DirectoryAPI(config, config.directory_id)
if config.action == "sync":
Expand Down
5 changes: 4 additions & 1 deletion bin/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright 2022 Akamai Technologies, Inc. All Rights Reserved.
Copyright 2023 Akamai Technologies, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -62,6 +62,9 @@ def __init__(self, config_values, configuration, flags=None):
listgrp_parser = subsub.add_parser("list", help="List all groups existing in an EAA directory")
listgrp_parser.add_argument('--groups', '-g', action='store_true', default=True, help="Display users")
listgrp_parser.add_argument('--users', '-u', action='store_true', default=False, help="Display groups")
listgrp_parser.add_argument('--json', action='store_true', default=False, help="Output as JSON")
listgrp_parser.add_argument('--tail', '-f', action='store_true', default=False,
help="Keep watching directory list, do not exit until Control+C/SIGTERM")
listgrp_parser.add_argument('search_pattern', nargs='?', help="Search pattern")

addgrp_parser = subsub.add_parser("addgroup", help="Add Group")
Expand Down
2 changes: 1 addition & 1 deletion cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"commands": [
{
"name": "eaa",
"version": "0.6.0",
"version": "0.6.1",
"description": "Akamai CLI for Enterprise Application Access (EAA)"
}
]
Expand Down
10 changes: 8 additions & 2 deletions libeaa/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"""

#: cli-eaa version [PEP 8]
__version__ = '0.6.0'
__version__ = '0.6.1'

import sys
from threading import Event
Expand Down Expand Up @@ -216,7 +216,6 @@ def __init__(self, config=None, api=API_Version.Legacy):
)
self._session.mount("https://", siem_api_adapter)
else: # EAA {OPEN} API
# TODO handle ambiguity when multiple contract ID are in use
self._baseurl = 'https://%s/crux/v1/' % edgerc.get(section, 'host')
self._session = requests.Session()
self._session.auth = EdgeGridAuth.from_edgerc(edgerc, section)
Expand All @@ -237,10 +236,17 @@ def user_agent(self):
return f"{self._config.ua_prefix} cli-eaa/{__version__}"

def build_params(self, params=None):
"""
Merge parameters passed as function argument with arguments from the configuration.
Return a dictionnary Requests can consume as `params` argument
"""
final_params = {"ua": self.user_agent()}
final_params.update(self.extra_qs)
if hasattr(self._config, 'contract_id') and self._config.contract_id:
final_params.update({'contractId': self._config.contract_id})
if hasattr(self._config, 'accountkey') and self._config.accountkey:
final_params.update({'accountSwitchKey': self._config.accountkey})
if isinstance(params, dict):
final_params.update(params)
return final_params
Expand Down
132 changes: 112 additions & 20 deletions libeaa/directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,56 @@
import requests
import datetime
import time
import json

# cli-eaa modules
import util
from common import cli, BaseAPI, EAAItem


class DirectoryStatus(Enum):
not_configured = 1
config_incomplete = 2
agent_not_assigned = 3
agent_not_reachable = 4
configured = 5
not_reachable = 6
success = 7


class Status(Enum):
not_added = 1
added = 2
no_connector = 3
pending = 4
not_reachable = 5
ok = 6


class SyncState(Enum):
Dirty = 1
ConnectorSync = 2
ConnectorSyncError = 3
CloudZoneSync = 4
CloudZoneSyncErr = 5
Synchronized = 6


class Service(Enum):
ActiveDirectory = 1
LDAP = 2
Okta = 3
PingOne = 4
SAML = 5
Cloud = 6
OneLogin = 7
Google = 8
Akamai = 9
AkamaiMSP = 10
LDS = 11
SCIM = 12


class DirectoryAPI(BaseAPI):
"""
Interact with EAA directory configurations.
Expand All @@ -34,9 +78,6 @@ class DirectoryAPI(BaseAPI):
# LDAP: 10,
# ActiveDirectory: 108

class DirectoryStatus(Enum):
Status3 = 3

def __init__(self, configuration, directory_moniker=None):
super(DirectoryAPI, self).__init__(configuration, BaseAPI.API_Version.OpenAPI)
self._directory = None
Expand Down Expand Up @@ -77,7 +118,37 @@ def list_users(self, search=None):
ln=u.get('last_name')
))

def list_directories(self):
def list_directories(self, interval=300, stop_event=None):
"""
List directories configured in the tenant.
Args:
follow (bool): Never stop until Control+C or SIGTERM is received
interval (float): Interval in seconds between pulling the API, default is 5 minutes (300s)
stop_event (Event): Main program stop event allowing the function
to stop at the earliest possible
"""
while True or (stop_event and not stop_event.is_set()):
try:
start = time.time()
self.list_directories_once()
if self._config.tail:
sleep_time = interval - (time.time() - start)
if sleep_time > 0:
stop_event.wait(sleep_time)
else:
logging.error(f"The EAA Directory API is slow to respond (could be also a proxy in the middle),"
f" holding for {sleep_time} sec.")
stop_event.wait(sleep_time)
else:
break
except Exception as e:
if self._config.tail:
logging.error(f"General exception {e}, since we are in follow mode (--tail), we keep going.")
else:
raise

def list_directories_once(self):
if self._directory_id:
if self._config.users:
if self._config.search_pattern and not self._config.batch:
Expand All @@ -92,24 +163,45 @@ def list_directories(self):
if resp.status_code != 200:
logging.error("Error retrieve directories (%s)" % resp.status_code)
resj = resp.json()
# print(resj)
if not self._config.batch:
cli.header("#dir_id,dir_name,status,user_count")
if not self._config.batch and not self._config.json:
cli.header("#dir_id,dir_name,status,user_count,group_count")
total_dir = 0
dt = datetime.datetime.now(tz=datetime.timezone.utc)
for total_dir, d in enumerate(resj.get("objects"), start=1):
cli.print("{scheme}{dirid},{name},{status},{user_count}".format(
scheme=EAAItem.Type.Directory.scheme,
dirid=d.get("uuid_url"),
name=d.get("name"),
status=d.get("directory_status"),
user_count=d.get("user_count"))
)
if total_dir == 0:
cli.footer("No EAA Directory configuration found.")
elif total_dir == 1:
cli.footer("One EAA Directory configuration found.")
else:
cli.footer("%d EAA Directory configurations found." % total_dir)
output = dict()
output["dir_id"] = EAAItem.Type.Directory.scheme + d.get("uuid_url")
output["service"] = Service(d.get("service")).name
output["name"] = d.get("name")
output["datetime"] = dt.isoformat()
output["enabled"] = d.get("status") == 1
output["connector_count"] = len(d.get("agents"))
output["directory_status"] = Status(d.get("directory_status")).name
output["group_count"] = d.get("user_count")
output["user_count"] = d.get("group_count")
output["last_sync"] = d.get("last_sync")
if d.get("agents"):
output["connectors"] = d.get("agents")

if self._config.json:
cli.print(json.dumps(output))
else:
cli.print("{scheme}{dirid},{name},{status},{directory_status},{user_count},{group_count}".format(
scheme=EAAItem.Type.Directory.scheme,
dirid=d.get("uuid_url"),
name=d.get("name"),
status=d.get("status"),
directory_status=Status(d.get("directory_status")).name,
group_count=d.get("user_count"),
user_count=d.get("group_count"))
)

if not self._config.json:
if total_dir == 0:
cli.footer("No EAA Directory configuration found.")
elif total_dir == 1:
cli.footer("One EAA Directory configuration found.")
else:
cli.footer("%d EAA Directory configurations found." % total_dir)

def delgroup(self, group_id):
raise NotImplementedError("Group deletion is not implemented")
Expand Down
17 changes: 17 additions & 0 deletions test/cli-eaa.bats
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env bats

# see https://bats-core.readthedocs.io/
# Make sure you set the AKAMAI_EDGERC_SECTION environment
# Before running the script

CLI="python3 ${BATS_TEST_DIRNAME}/../bin/akamai-eaa"
SCRIPT_DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )"
Expand Down Expand Up @@ -84,3 +86,18 @@ teardown() {
[ "$?" -eq 0 ]
}

@test "Create and patch app with JQ" {
result="$(${BATS_TEST_DIRNAME}/createapp.bash)"
[ "$?" -eq 0 ]
}


@test "Directory list (csv)" {
result="$(${CLI} dir list)"
[ "$?" -eq 0 ]
}

@test "Directory list (JSON)" {
result="$(${CLI} dir list --json|jq .)"
[ "$?" -eq 0 ]
}
21 changes: 20 additions & 1 deletion test/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
. ./venv/bin/activate # or any other location/preference
pip install nose nose2-html-report
Run the test
Run all tests
.. code-block:: bash
cd test
Expand Down Expand Up @@ -334,6 +334,25 @@ def test_list_directories(self):
self.assertEqual(cmd.returncode, 0, 'return code must be 0')


class TestDirectory(CliEAATest):

def test_directory_health_tail(self):
"""
Run directory command to fetch full health statuses in follow mode
We use the RAW format for convenience (easier to read in the output)
"""
cmd = self.cli_run('-d', '-v', 'dir', 'list', '--json', '--tail')
time.sleep(20) # Long enough to collect some data
cmd.send_signal(signal.SIGINT)
stdout, stderr = cmd.communicate(timeout=50.0)
CliEAATest.cli_print("rc: ", cmd.returncode)
for line in stdout.splitlines():
CliEAATest.cli_print("stdout>", line)
for line in stderr.splitlines():
CliEAATest.cli_print("stderr>", line)
self.assertGreater(len(stdout), 0, "No directory health output")


class TestCliEAA(CliEAATest):
"""
General commands of the CLI like version or help
Expand Down

0 comments on commit 9486cc0

Please sign in to comment.