Skip to content

Commit

Permalink
Feature batch-create, multi-hosts (#27)
Browse files Browse the repository at this point in the history
#### FEATURES/ENHANCEMENTS:

- Support multi-hosts command to add multiple hostnames and respective origins to a single delivery/property configuration and include all of those hostnames into the new security configuration
  - The command requires a new input file in a CSV format
  - Support three standard akamai product: prd_SPM, prd_Fresca, prd_API_Accel
- Support batch-create command to add multiple hostnames and respective origins to one or more delivery/property configurations and optionally add all of those hostnames to an existing security configuration and policy match target
  - The command requires a new input file in a CSV format
  - Support three standard akamai product: prd_SPM, prd_Fresca, prd_API_Accel

#### MISC:

- Allow short arguments i.e. both --file and -f will work
- Rename sample setup files that are easier to identify for each command
- Display proper version for --version and -h command
- Provide sample setup files via new command fetch-sample-templates 

---------

Co-authored-by: Peak Wongcharoen <pwongcha@akamai.com>, Julie Sulkin <jsulkin@akamai.com>
  • Loading branch information
juliesulkin and juliesulkin committed Mar 31, 2023
1 parent 157d464 commit c738ffd
Show file tree
Hide file tree
Showing 31 changed files with 4,420 additions and 484 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
*logs*
.DS_Store
.venv
.DS_Store
.vscode
.scripts
.templates
sample_templates
16 changes: 11 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# RELEASE NOTES

## 2.1.0 (February 28, 2023)
## 2.2.0

#### FEATURES/ENHANCEMENTS:

* Support for secure by default certificates on both single-host and create setup types
* Updated setup.json and setup_single_host.json templates to include secure_by_default option

- Support multi-hosts command to add multiple hostnames and respective origins to a single delivery/property configuration and include all of those hostnames into the new security configuration
- The command requires a new input file in a CSV format
- Support three standard akamai product: prd_SPM, prd_Fresca, prd_API_Accel
- Support batch-create command to add multiple hostnames and respective origins to one or more delivery/property configurations and optionally add all of those hostnames to an existing security configuration and policy match target
- The command requires a new input file in a CSV format
- Support three standard akamai product: prd_SPM, prd_Fresca, prd_API_Accel

#### MISC:

* Added validation check to hostnames
- Allow short arguments i.e. both --file and -f will work
- Rename sample setup files that are easier to identify for each command
- Display proper version for --version and -h command
252 changes: 188 additions & 64 deletions README.md

Large diffs are not rendered by default.

496 changes: 411 additions & 85 deletions bin/akamai-onboard.py

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions bin/model/multi_hosts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""
Copyright 2022 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from __future__ import annotations

from dataclasses import dataclass
from dataclasses import field

from exceptions import setup_logger


logger = setup_logger()


@dataclass
class MultiHosts:
property_name: str
contract_id: str
product_id: str
public_hostnames: list
secure_by_default: bool
edge_hostname: str
notification_emails: list
individual_cpcode: bool

origin_default: str = ''
group_id: str = ''
edge_hostname_mode: str = 'use_existing_edgehostname'
edge_hostname_id: int = 0

# general
secure_network: str = 'ENHANCED_TLS'
rule_format: str = 'latest'
create_new_cpcode: bool = True
individual_cpcode: bool = False
new_cpcode_name: list[int] = field(default_factory=list)
version_notes: str = 'Initial Version'

onboard_default_cpcode: int = 0
onboard_property_id: str = ''

# setup input files
use_file: bool = True
source_template_file: str = ''
source_values_file: str = ''

# pipeline
use_folder: bool = False
folder_path: str = ''
env_name: str = ''

# cert
secure_by_default_new_ehn: bool = True
secure_by_default_use_existing_ehn: str = ''
use_existing_enrollment_id: bool = False
existing_enrollment_id: int = 0
create_new_ssl_cert: bool = False
ssl_cert_template_file: str = ''
ssl_cert_template_values: str = ''
temp_existing_edge_hostname: str = 'xxx'

# waf info
waf_config_name: str = 'WAF Security File'
create_new_security_config: bool = True
add_selected_host: bool = True
update_match_target: bool = True
waf_match_target_id: str = ''
onboard_waf_config_id: int = 0
onboard_waf_prev_version: int = 0
onboard_waf_config_version: int = 0
policy_id: str = ''
policy_name: str = 'Default'
target_seq: int = 0
target_id: int = 0

# Staging activation
activate_property_staging: bool = True
activate_waf_policy_staging: bool = True

# Production activation
activate_property_production: bool = True
activate_waf_policy_production: bool = True

def add(self, name: str):
self.new_cpcode_name.append(name)
logger.debug(f'{self.new_cpcode_name=}')

def update_origin_default(self, origin_hostname: str):
self.origin_default = origin_hostname
131 changes: 131 additions & 0 deletions bin/onboard_batch_create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""
Copyright 2019 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from __future__ import annotations

import os
from pathlib import Path

from exceptions import setup_logger
logger = setup_logger()


class onboard:
# Initialize the object
def __init__(self, config, click_args):
# Read values from setup.json or --file
# Certain values (onboard_) are updated in main processing later
try:
self.property_name = []
self.csv_loc = click_args['csv']
self.property_list = []
self.valid_csv = True
self.csv_dict = []
self.secure_network = click_args['network']
self.ehn_suffix = '.edgekey.net'
if self.secure_network == 'STANDARD_TLS':
self.ehn_suffix = 'edgesuite.net'
self.contract_id = click_args['contract']
self.group_id = click_args['group']
self.product_id = click_args['product']
self.rule_format = click_args['rule_format']
self.create_new_cpcode = True
self.source_template_file = click_args['template']
self.source_template_file = self.get_actual_location(self.source_template_file)
self.level_0_rules = []

self.public_hostnames = []

self.onboard_property_id = None
self.onboard_default_cpcode = 0
self.edge_hostname_id = 0
self.edge_hostname_list = []
# Edge hostname values
if click_args['secure_by_default']:
self.edge_hostname_mode = 'secure_by_default'
else:
self.edge_hostname_mode = 'use_existing_edgehostname'

# WAF values
if click_args['waf_config']:
self.add_selected_host = True
else:
self.add_selected_host = False

self.waf_config_name = click_args['waf_config']

if click_args['waf_match_target']:
self.update_match_target = True
else:
self.update_match_target = False
self.waf_match_target_id = click_args['waf_match_target']
if isinstance(self.waf_match_target_id, str):
if self.waf_match_target_id == '':
self.waf_match_target_id = 0
else:
self.waf_match_target_id = int(self.waf_match_target_id)

self.onboard_waf_config_id = None
self.onboard_waf_config_version = None
self.onboard_waf_prev_version = None

self.activate_property_staging = False
self.activate_waf_policy_staging = False
self.activate_property_production = False
self.activate_waf_policy_production = False

# Activation values
if 'delivery-staging' in click_args['activate']:
self.activate_property_staging = True
if 'waf-staging' in click_args['activate']:
self.activate_waf_policy_staging = True
if 'delivery-production' in click_args['activate']:
self.activate_property_production = True
if 'waf-production' in click_args['activate']:
self.activate_waf_policy_production = True

if click_args['email']:
self.notification_emails = click_args['email']
else:
self.notification_emails = ['noreply@akamai.com']
self.version_notes = 'Created using Onboard CLI'

# Read config object that contains the command line parameters
if not config.edgerc:
if not os.getenv('AKAMAI_EDGERC'):
self.edgerc = os.path.join(os.path.expanduser('~'), '.edgerc')
else:
self.edgerc = os.getenv('AKAMAI_EDGERC')
else:
self.edgerc = config.edgerc

if not config.section:
if not os.getenv('AKAMAI_EDGERC_SECTION'):
self.section = 'onboard'
else:
self.section = os.getenv('AKAMAI_EDGERC_SECTION')
else:
self.section = config.section

except KeyError as k:
print('\nInput file is missing ' + str(k))
exit(-1)

def get_actual_location(self, file_location: str) -> str:
abs_file_location = file_location
home = str(Path.home())
if '~' in file_location:
file_location = file_location.replace('~', '')
abs_file_location = f'{home}/{file_location}'

return abs_file_location
101 changes: 101 additions & 0 deletions bin/onboard_multi_hosts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""
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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from __future__ import annotations

import json
import sys
from pathlib import Path

from exceptions import get_cli_root_directory
from exceptions import setup_logger

logger = setup_logger()
root = get_cli_root_directory()


class onboard:
def __init__(self, json_input: dict):
try:
self.contract_id = json_input['property_info']['contract_id']
self.product_id = json_input['property_info']['product_id']
self.property_name = json_input['property_info']['property_name']
self.individual_cpcode = json_input['property_info']['individual_cpcode']

self.edge_hostname = json_input['edge_hostname']['use_existing_edge_hostname']
self.existing_enrollment_id = json_input['edge_hostname']['create_from_existing_enrollment_id']

try:
self.secure_by_default = json_input['edge_hostname']['secure_by_default']
except KeyError as k:
logger.warning('You are not using the latest template. Please use new setup.json template if you want to use secure by default')
self.secure_by_default = False

# The cpcode name contains one or more of these special characters ^ _ , # % ' \" ",
self.new_cpcode_name = self.property_name.replace('_', ' ')

# Security
self.create_new_security_config = json_input['update_waf_info']['create_new_security_config']
self.waf_config_name = ''
if len(json_input['update_waf_info']['waf_config_name']) > 0:
self.waf_config_name = json_input['update_waf_info']['waf_config_name']
self.activate_production = json_input['activate_production']
self.notification_emails = json_input['notification_emails']
except KeyError as k:
sys.exit(logger.error(f'Input file is missing {k}'))

if isinstance(self.existing_enrollment_id, str):
if self.existing_enrollment_id == '':
self.existing_enrollment_id = 0
else:
self.existing_enrollment_id = int(self.existing_enrollment_id)

try:
self.version_notes = json_input['property_info']['version_notes']
except:
self.version_notes = ''

def write_variable_json(self, default_origin: str, cp_code: int) -> None:
"""
Override origin server inside templates/variables.json which is hidden from user
"""
var = {}
var['origin_default'] = default_origin
var['cp_code'] = cp_code

# override when run via CLI
variable_file = Path(root, 'templates/akamai_product_templates/multi-hosts/variables.json')
with variable_file.open('w') as file:
json.dump(var, file, indent=4)

# override when run via python script
with Path('logs/variables.json').absolute().open('w') as file:
json.dump(var, file, indent=4)

def get_product_template(self, src_file: str) -> dict:
with open(src_file) as f:
rules = json.load(f)
logger.debug(json.dumps(rules, indent=4))
return rules

def override_product_template(self, onboard, rules: dict) -> None:
# override when run via CLI
template_file = Path(root, 'templates/akamai_product_templates/multi-hosts/multiple_hosts.json')
with template_file.open('w') as file:
json.dump(rules, file, indent=4)
onboard.source_template_file = f'{template_file}'

# override when run via python script
with Path('logs/multiple_hosts.json').absolute().open('w') as file:
json.dump(rules, file, indent=2)
return None

0 comments on commit c738ffd

Please sign in to comment.