-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(akamai): implement cloudlets v3 (refs #13)
- Loading branch information
Showing
22 changed files
with
1,463 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,3 +129,4 @@ dmypy.json | |
.pyre/ | ||
.vscode | ||
*.code-workspace | ||
.bossmancache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .client import * | ||
from .data import * | ||
from .resourcetype import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
from datetime import datetime | ||
import dateutil.parser | ||
from typing import List | ||
from cattr import Converter | ||
|
||
from bossman.logging import get_class_logger | ||
from bossman.plugins.akamai.lib.edgegrid import Session | ||
from .data import * | ||
from .error import * | ||
|
||
class CloudletAPIV3Client: | ||
def __init__(self, edgerc, section, switch_key=None, **kwargs): | ||
self.logger = get_class_logger(self) | ||
self.session = Session(edgerc, section, switch_key=switch_key, **kwargs) | ||
self.converter = Converter() | ||
self.converter.register_structure_hook(datetime, lambda d, t: dateutil.parser.isoparse(d)) | ||
|
||
def get_policy_by_name(self, name): | ||
self.logger.debug("get_policy_by_name name={name}".format(name=name)) | ||
id = None | ||
policies = self.get_policies() | ||
for policy in policies: | ||
if policy.name == name: | ||
return policy | ||
raise PolicyNameNotFound(name) | ||
|
||
def get_policies(self) -> List[SharedPolicy]: | ||
self.logger.debug("get_policies") | ||
response = self.session.get("/cloudlets/v3/policies") | ||
if response.status_code != 200: | ||
raise CloudletAPIV3Error(response.json()) | ||
# import json | ||
# print(json.dumps(response.json(), indent=2)) | ||
response = self.converter.structure(response.json(), GetPoliciesResponse) | ||
return response.content | ||
|
||
def get_policy_version(self, policyId: int, policyVersion: int) -> SharedPolicyVersion: | ||
self.logger.debug("get_policy_version policyId={policyId}, policyVersion={policyVersion}".format(policyId=policyId, policyVersion=policyVersion)) | ||
response = self.session.get("/cloudlets/v3/policies/{policyId}/versions/{policyVersion}".format(policyId=policyId, policyVersion=policyVersion)) | ||
if response.status_code != 200: | ||
raise CloudletAPIV3Error(response.json()) | ||
return self.converter.structure(response.json(), SharedPolicyVersion) | ||
|
||
def get_latest_policy_version(self, policyId: int) -> Optional[SharedPolicyVersion]: | ||
self.logger.debug("get_policy_version policyId={policyId}".format(policyId=policyId)) | ||
response = self.session.get("/cloudlets/v3/policies/{policyId}/versions".format(policyId=policyId), params=dict(size=10)) | ||
if response.status_code != 200: | ||
raise CloudletAPIV3Error(response.json()) | ||
policy_versions = self.converter.structure(response.json(), GetPolicyVersionsResponse) | ||
if len(policy_versions.content): | ||
return policy_versions.content[0] | ||
return None | ||
|
||
def get_latest_policy_versions(self, policyId: int, count: int) -> List[SharedPolicyVersion]: | ||
self.logger.debug("get_latest_policy_versions policyId={policyId}, count={count}".format(policyId=policyId, count=count)) | ||
response = self.session.get("/cloudlets/v3/policies/{policyId}/versions".format(policyId=policyId), params=dict(size=count)) | ||
if response.status_code != 200: | ||
raise CloudletAPIV3Error(response.json()) | ||
policy_versions = self.converter.structure(response.json(), GetPolicyVersionsResponse) | ||
return policy_versions.content | ||
|
||
def create_policy(self, policyName: str, description: str, cloudletType: SharedPolicyCloudletType, groupId: int) -> SharedPolicy: | ||
self.logger.debug("create_policy policyName={policyName}".format(policyName=policyName)) | ||
response = self.session.post("/cloudlets/v3/policies", json=dict( | ||
name=policyName, | ||
cloudletType=cloudletType.value, | ||
groupId=groupId, | ||
description=description, | ||
)) | ||
if response.status_code != 201: | ||
raise CloudletAPIV3Error(response.json()) | ||
return self.converter.structure(response.json(), SharedPolicy) | ||
|
||
def update_policy(self, policyId: int, description: str, groupId: int) -> SharedPolicy: | ||
self.logger.debug("update_policy policyName={policyId}".format(policyId=policyId)) | ||
response = self.session.put("/cloudlets/v3/policies/{policyId}".format(policyId=policyId), json=dict( | ||
groupId=groupId, | ||
description=description, | ||
)) | ||
if response.status_code != 202: | ||
raise CloudletAPIV3Error(response.json()) | ||
return self.converter.structure(response.json(), SharedPolicy) | ||
|
||
def create_policy_version(self, policyId: int, description: str, matchRules: List[dict]) -> SharedPolicyVersion: | ||
self.logger.debug("create_policy_version policyId={policyId}".format(policyId=policyId)) | ||
response = self.session.post("/cloudlets/v3/policies/{policyId}/versions".format(policyId=policyId), json=dict( | ||
description=description, | ||
matchRules=matchRules, | ||
)) | ||
if response.status_code != 201: | ||
raise CloudletAPIV3Error(response.json()) | ||
return self.converter.structure(response.json(), SharedPolicyVersion) | ||
|
||
def activate_policy_version(self, policyId: int, policyVersion: int, network: Network) -> SharedPolicyActivation: | ||
self.logger.debug("activate_policy_version policyId={policyId}, policyVersion={policyVersion}".format(policyId=policyId, policyVersion=policyVersion)) | ||
response = self.session.post("/cloudlets/v3/policies/{policyId}/activations".format(policyId=policyId), json=dict( | ||
network=network.value, | ||
operation=SharedPolicyActivationOperation.ACTIVATION.value, | ||
policyVersion=policyVersion, | ||
)) | ||
if response.status_code != 202: | ||
result = response.json().get('errors')[0] | ||
raise CloudletAPIV3Error(result.get('detail')) | ||
return self.converter.structure(response.json(), SharedPolicyActivation) | ||
|
||
def get_policy_version_activation_status(self, activation: SharedPolicyActivation) -> SharedPolicyActivation: | ||
self.logger.debug("get_policy_version_activation_status policyId={policyId}, activationId={activationId}".format(policyId=activation.policyId, activationId=activation.id)) | ||
response = self.session.get("/cloudlets/v3/policies/{policyId}/activations/{activationId}".format(policyId=activation.policyId, activationId=activation.id)) | ||
if response.status_code != 200: | ||
raise CloudletAPIV3Error(response.json()) | ||
return self.converter.structure(response.json(), SharedPolicyActivation) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import re | ||
from dataclasses import dataclass | ||
from datetime import datetime | ||
from enum import Enum | ||
from typing import List, Optional | ||
|
||
|
||
|
||
class Network(Enum): | ||
PRODUCTION = 'PRODUCTION' | ||
STAGING = 'STAGING' | ||
|
||
@property | ||
def alias(self) -> str: | ||
return re.sub("[aeiou]", "", self.value, flags=re.IGNORECASE)[:3].upper() | ||
|
||
@property | ||
def color(self) -> str: | ||
return "green" if self == Network.PRODUCTION else "magenta" | ||
|
||
class SharedPolicyActivationOperation(Enum): | ||
ACTIVATION = 'ACTIVATION' | ||
DEACTIVATION = 'DEACTIVATION' | ||
|
||
class SharedPolicyActivationStatus(Enum): | ||
IN_PROGRESS = 'IN_PROGRESS' | ||
SUCCESS = 'SUCCESS' | ||
FAILED = 'FAILED' | ||
|
||
class SharedPolicyPolicyType(Enum): | ||
SHARED = 'SHARED' | ||
|
||
class SharedPolicyCloudletType(Enum): | ||
ER = 'ER' | ||
FR = 'FR' | ||
AS = 'AS' | ||
|
||
@dataclass | ||
class SharedPolicyActivation: | ||
id: int | ||
createdBy: str | ||
createdDate: datetime | ||
finishDate: Optional[datetime] | ||
network: Network | ||
operation: SharedPolicyActivationOperation | ||
status: SharedPolicyActivationStatus | ||
policyId: int | ||
policyVersion: int | ||
policyVersionDeleted: bool | ||
|
||
@dataclass | ||
class SharedPolicyCurrentActivationsNetwork: | ||
effective: Optional[SharedPolicyActivation] = None | ||
latest: Optional[SharedPolicyActivation] = None | ||
|
||
@dataclass | ||
class SharedPolicyCurrentActivations: | ||
production: SharedPolicyCurrentActivationsNetwork = None | ||
staging: SharedPolicyCurrentActivationsNetwork = None | ||
|
||
@dataclass | ||
class SharedPolicy: | ||
id: int | ||
name: str | ||
description: str | ||
policyType: SharedPolicyPolicyType | ||
cloudletType: SharedPolicyCloudletType | ||
createdBy: str | ||
createdDate: datetime | ||
currentActivations: SharedPolicyCurrentActivations | ||
groupId: int | ||
modifiedBy: str | ||
modifiedDate: datetime | ||
|
||
@dataclass | ||
class GetPoliciesResponse: | ||
content: List[SharedPolicy] | ||
|
||
@dataclass | ||
class SharedPolicyVersion: | ||
policyId: int | ||
version: int | ||
createdBy: str | ||
createdDate: datetime | ||
modifiedBy: str | ||
modifiedDate: datetime | ||
description: str | ||
matchRules: Optional[List[dict]] = None | ||
|
||
@dataclass | ||
class GetPolicyVersionsResponse: | ||
content: List[SharedPolicyVersion] | ||
|
||
@dataclass | ||
class SharedPolicyAsCode: | ||
""" | ||
Subset of SharedPolicy that must be versioned in order to support CRUD. | ||
""" | ||
description: str | ||
groupId: int | ||
cloudletType: SharedPolicyCloudletType | ||
matchRules: Optional[List[dict]] = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from bossman.errors import BossmanError | ||
|
||
class CloudletAPIV3Error(BossmanError): | ||
pass | ||
|
||
class PolicyNameNotFound(CloudletAPIV3Error): | ||
pass | ||
|
||
class PolicyVersionValidationError(CloudletAPIV3Error): | ||
pass | ||
|
||
class PolicyActivationAlreadyPendingError(CloudletAPIV3Error): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import pathlib | ||
from bossman.abc import ResourceABC | ||
|
||
class SharedPolicyResource(ResourceABC): | ||
def __init__(self, path, **kwargs): | ||
super(SharedPolicyResource, self).__init__(path) | ||
self.__name = kwargs.get("name") | ||
|
||
@property | ||
def name(self): | ||
return self.__name | ||
|
||
@property | ||
def policy_path(self): | ||
# All operations use unix-style paths; this is important | ||
return str(pathlib.PurePosixPath(self.path) / "policy.json") | ||
|
||
@property | ||
def paths(self): | ||
return (self.policy_path,) | ||
|
||
def __rich__(self): | ||
prefix = self.path.replace(self.name, "") | ||
return "[grey53]{}[/][yellow]{}[/]".format(prefix, self.name) |
Oops, something went wrong.