Skip to content

Commit

Permalink
Merge pull request #46 from akamai/rc0.6.8
Browse files Browse the repository at this point in the history
Release 0.6.8
  • Loading branch information
bitonio authored May 24, 2024
2 parents c58e156 + 67ed4f3 commit ad1cc13
Show file tree
Hide file tree
Showing 16 changed files with 645 additions and 77 deletions.
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,23 @@ __pycache__
tags
docs/examples/includes.akdemo/*
*.env
**/dist/*
*.log

# Terraform
**/.terraform/*
*.tfstate
*.tfstate.*
crash.log
crash.*.log
*.tfvars
*.tfvars.json
!example*.tfvars
!example*.tfvars.json
override.tf
override.tf.json
*_override.tf
*_override.tf.json
.terraformrc
terraform.rc
.terraform.lock.hcl
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.6.7
0.6.8
102 changes: 33 additions & 69 deletions bin/akamai-eaa
Original file line number Diff line number Diff line change
Expand Up @@ -60,78 +60,42 @@ class SearchAPI(BaseAPI):
def __init__(self, config):
super(SearchAPI, self).__init__(config, api=BaseAPI.API_Version.OpenAPI)

def search_app(self, search_pattern):
"""
Search for a particular application pattern, or all if no pattern is provided.
Corresponding API documentation: https://techdocs.akamai.com/eaa-api/reference/get-apps
"""

url_params = {'limit': SearchAPI.SCAN_LIMIT, 'expand': 'true'}
first_search = self.get('mgmt-pop/apps', params=url_params)
data = first_search.json()
def search_app(self, search_pattern: str=None):
app_api = ApplicationAPI(config, BaseAPI.API_Version.OpenAPIv3)
all_apps = app_api.list(fields=['app_type', 'name', 'host', 'cname', 'cert', 'resource_status'])
app_count = len(all_apps)
app_found = 0
app_scanned = 0

# CLI ouput header
cli.header('#app_id,type,name,host,cname,cert_id,status,reachable')
stats = self.process_page(data, search_pattern)
app_scanned += stats[0]
app_found += stats[1]

if data.get("meta"):

app_count = data.get("meta").get("total_count")
page_offset = data.get("meta").get("offset")
page_limit = data.get("meta").get("limit")
page_total = ceil(app_count / page_limit)

logging.debug("app_count: {}, scanned: {}, offset: {}, limit: {}, pages: {}".format(
app_count, app_scanned, page_offset, page_limit, page_total))

for page in range(1, page_total):
logging.debug("Loading application page {} of {}".format(page, page_total))
url_params['offset'] = page * page_limit
search = self.get('mgmt-pop/apps', params=url_params)
stats = self.process_page(search.json(), search_pattern)
app_scanned += stats[0]
app_found += stats[1]

# CLI ouput footer
if not config.batch:
if app_found != app_count:
cli.footer("Found %s app(s), total %s app(s)" % (app_found, app_count))
else:
cli.footer("%s app(s)" % app_count)

def process_page(self, data, search_pattern):
app_found = 0
app_scanned = 0

if data.get("meta"):
for a in data.get('objects', []):
app_scanned += 1
if not search_pattern or (
search_pattern and (
fnmatch.fnmatch(a.get('name') or "", "*%s*" % search_pattern) or
fnmatch.fnmatch(a.get('host') or "", "*%s*" % search_pattern) or
fnmatch.fnmatch(a.get('cname') or "", "*%s*" % search_pattern)
)
):
cli.print(('{scheme}{app_id},{app_type},{name},{host},{cname},'
'{cert_scheme}{cert_id},{status},{reach}').format(
scheme=EAAItem.Type.Application.scheme,
app_id=a.get('uuid_url'),
app_type=ApplicationAPI.Type(a.get('app_type')).name,
name=a.get('name'),
host=a.get('host'),
cname=a.get('cname'),
cert_scheme=EAAItem.Type.Certificate.scheme,
cert_id=a.get('cert'),
status=ApplicationAPI.Status(a.get('app_status')).name,
reach=('Y' if a.get('resource_status', {}).get('host_reachable') else 'F'))
)
app_found += 1
return app_scanned, app_found
for a in all_apps:
if not search_pattern or (
search_pattern and (
fnmatch.fnmatch(a.get('name') or "", "*%s*" % search_pattern) or
fnmatch.fnmatch(a.get('host') or "", "*%s*" % search_pattern) or
fnmatch.fnmatch(a.get('cname') or "", "*%s*" % search_pattern)
)
):
app_found += 1
cli.print(('{scheme}{app_id},{app_type},{name},{host},{cname},'
'{cert_scheme}{cert_id},{status},{reach}').format(
scheme=EAAItem.Type.Application.scheme,
app_id=a.get('uuid_url'),
app_type=ApplicationAPI.Type(a.get('app_type')).name,
name=a.get('name'),
host=a.get('host'),
cname=a.get('cname'),
cert_scheme=EAAItem.Type.Certificate.scheme if a.get('cert') else '',
cert_id=a.get('cert') or '-',
status=ApplicationAPI.Status(a.get('resource_status', {}).get('app_status')).name or "-",
reach=('Y' if a.get('resource_status', {}).get('host_reachable') else 'F'))
)

# CLI ouput footer
if not config.batch:
if app_found != app_count:
cli.footer("Found %s app(s), total %s app(s)" % (app_found, app_count))
else:
cli.footer("%s app(s)" % app_count)


class ReportingAPI(BaseAPI):
Expand Down
17 changes: 17 additions & 0 deletions bin/akamai-eaa-venv
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

# Wrapper to enable Python virtual env on the fly, run the command
# and then deactivate it.

DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

VENV_ACTIVATE="${DIR}/../venv/bin/activate"

if [ -f "${VENV_ACTIVATE}" ]; then
. "${VENV_ACTIVATE}"
"${DIR}/akamai-eaa" "$@"
deactivate
else
echo "$0: Cannot activate Python virtual environment: ${VENV_ACTIVATE}"
exit 2
fi
21 changes: 20 additions & 1 deletion bin/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@
from error import rc_error, cli_exit_with_error


def strtobool(value):
"""
We could do
from distutils.util import strtobool
Yet, it won't be a good long term solution:
https://docs.python.org/3.10/library/distutils.html
"""
if isinstance(value, bool):
return value
value = value.lower()
if value in ('y', 'yes', 't', 'true', 'on', '1'):
return True
elif value in ('n', 'no', 'f', 'false', 'off', '0'):
return False
else:
raise ValueError("invalid input value %r" % (value,))


class EdgeGridConfig():

parser = argparse.ArgumentParser(prog="akamai eaa",
Expand Down Expand Up @@ -209,7 +227,8 @@ def __init__(self, config_values, configuration, flags=None):

parser.add_argument('--batch', '-b', default=False, action='store_true',
help='Batch mode, remove the extra header/footer in lists.')
parser.add_argument('--debug', '-d', default=False, action='count', help=' Debug mode (log HTTP headers)')
parser.add_argument('--debug', '-d', action="store_true", default=strtobool(os.environ.get("CLIEAA_DEBUG", False)),
help=' Debug mode (log HTTP headers)')
parser.add_argument('--edgerc', '-e',
default=os.environ.get('AKAMAI_EDGERC', '~/.edgerc'),
metavar='credentials_file',
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.7",
"version": "0.6.8",
"description": "Akamai CLI for Enterprise Application Access (EAA)"
}
]
Expand Down
66 changes: 62 additions & 4 deletions libeaa/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import sys
import json
import os
import string
import random

# cli-eaa
from common import cli, BaseAPI, EAAInvalidMoniker, EAAItem, config, merge_dicts
Expand Down Expand Up @@ -262,6 +264,44 @@ def certificate_lookup(self, cn):
return cert.get('uuid_url')
return ""

def connector_lookup(self, name):
agents = self.get('mgmt-pop/agents?limit=100')
for agent in agents.json().get('objects'):
if agent.get('name') == name:
return agent.get('uuid_url')
return ""

def idp_lookup(self, name):
idps = self.get('mgmt-pop/idp?limit=100')
for idp in idps.json().get('objects'):
if idp.get('name') == name:
logger.debug(json.dumps(idp, indent=2))
return idp
return None

def directory_lookup(self, name):
directories = self.get('mgmt-pop/directories?limit=100')
for dir in directories.json().get('objects'):
if dir.get('name') == name:
logger.debug(json.dumps(dir, indent=2))
return dir
return None

def group_lookup(self, directory_uuid, group_name):
logger.debug(f"group_lookup: looking for {group_name} in directory {directory_uuid}...")
url = 'mgmt-pop/directories/{directory_uuid}/groups'.format(directory_uuid=directory_uuid)
groups = self.get(url)
for g in groups.json().get('objects'):
if g.get('name') == group_name:
logger.debug("### FOUND GROUP")
logger.debug(json.dumps(g, indent=2))
return g
return None

def random_string(self, length):
characters = string.ascii_lowercase + string.digits
return ''.join(random.choice(characters) for _ in range(length))

def parse_template(self, raw_config):
"""
Parse the EAA configuration as JINJA2 template
Expand All @@ -273,6 +313,11 @@ def parse_template(self, raw_config):
t.globals['AppDomainType'] = ApplicationAPI.Domain
t.globals['cli_cloudzone'] = self.cloudzone_lookup
t.globals['cli_certificate'] = self.certificate_lookup
t.globals['cli_connector'] = self.connector_lookup
t.globals['cli_idp'] = self.idp_lookup
t.globals['cli_directory'] = self.directory_lookup
t.globals['cli_group'] = self.group_lookup
t.globals['cli_randomstring'] = self.random_string
output = t.render(**dict(self._config.variables))
logger.debug("JSON Post-Template Render:")
for lineno, line in enumerate(output.splitlines()):
Expand Down Expand Up @@ -302,6 +347,12 @@ def create(self, raw_app_config):
app_config_create["client_app_mode"] = \
app_config.get('client_app_mode', ApplicationAPI.ClientMode.TCP.value)
newapp = self.post('mgmt-pop/apps', json=app_config_create)

if config.debug:
json_object = json.dumps(app_config_create, indent=4)
with open(f"1_POST_{app_config.get("name")}.json", "w") as outfile:
outfile.write(json_object)

logger.info("Create app core: %s %s" % (newapp.status_code, newapp.text))
if newapp.status_code != 200:
cli.exit(2)
Expand All @@ -313,6 +364,11 @@ def create(self, raw_app_config):
# Now we push everything else as a PUT (update)
self.put('mgmt-pop/apps/{applicationId}'.format(applicationId=app_moniker.uuid), json=app_config)

if config.debug:
json_object = json.dumps(app_config, indent=4)
with open(f"2_PUT_{app_config.get("name")}.json", "w") as outfile:
outfile.write(json_object)

# Sub-components of the application configuration definition

# --- Connectors
Expand Down Expand Up @@ -596,24 +652,26 @@ def deploy(self, app_moniker, comment=""):
if deploy.status_code != 200:
logger.error(deploy.text)

def list(self, details=False):
def list(self, fields: list=[], details: bool=False):
"""
List all applications (experimental)
:param details (bool): load the app details (SUPER SLOW)
"""

if self.api_ver != BaseAPI.API_Version.OpenAPIv3:
raise Exception("Unsupported API version")

app_fields = set(["uuid_url"] + fields)

total_count = None
page = 1
page_size = 25
page_size = 100
offset = 0
l = []

app_config_loader = ApplicationAPI(self._config, BaseAPI.API_Version.OpenAPI)
while total_count == None or len(l) < total_count:
r = self.get('mgmt-pop/apps', params={"offset": offset, "fields": "uuid_url",
r = self.get('mgmt-pop/apps', params={"offset": offset, "fields": ",".join(app_fields),
"limit": page_size, "offset": (page-1)*page_size})
j = r.json()
total_count = j.get('meta').get('total_count')
Expand All @@ -630,7 +688,7 @@ def stats_by_cloudzone(self):
raise Exception("Unsupported API version")

appcount_by_cloudzone = {}
for a in self.list(True):
for a in self.list(details=True):
scanned_cloudzone = a.get('popRegion')
if scanned_cloudzone not in appcount_by_cloudzone.keys():
appcount_by_cloudzone[scanned_cloudzone] = 0
Expand Down
2 changes: 1 addition & 1 deletion 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.7'
__version__ = '0.6.8'

import sys
from threading import Event
Expand Down
52 changes: 52 additions & 0 deletions terraform/cli-eaa-application/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# eaa-application terraform module

Terraform module allows to provision (create/update) or deprovision (delete) EAA application configuration in your Terraform code, relying on BASH and `cli-eaa`.

It leverages Terraform `local-exec` provisioner which are "a Last Resort": keep this in mind.

For native EAA Terraform, we recommend https://github.com/akamai/terraform-eaa.

## Getting started

See [examples](examples/) sub-directory for details.

```HCL
module "my-eaa-app" {
# You may pin a tagged version here, or use the lasted main branch
source = "https://github.com/akamai/cli-eaa/terraform/cli-eaa-application"
# API Credentials
openapi_credentials = {
access_token = "akab-abcd"
client_secret = "abcdef"
client_token = "akab-abcdef"
hostname = "akab-abcdef.luna.akamaiapis.net"
}
# Authentication
auth_idp_name = "MyIdP"
auth_directory_name = "MyActiveDirectory"
auth_group_names = ["MyGroup"]
# Connectors
connector_names = ["con-1", "con-2"]
# Application info
cloudzone_name = "Client-US-East"
app_name = "My EAA application"
app_description = " created with Terraform"
tunnel_destination = {
host_or_ip = "www.akamai.com"
ports = "443"
}
}
```

## Limitations

- Only one identity directory
- Only client-app type=tunnel
- Only Akamai external hostnames (.go.akamai-access.com)
Loading

0 comments on commit ad1cc13

Please sign in to comment.