Skip to content

Commit a1ff909

Browse files
author
Mark Beacom
authored
Project provisioning (#72)
* Add project creation flow * Update formatting * Add repl config * Adjust creation * Adjust region handling * Finishing adjustments to creation flow for projects, creds, replication, etc * Adjust linting
1 parent 932c93b commit a1ff909

File tree

5 files changed

+238
-7
lines changed

5 files changed

+238
-7
lines changed

cloudendure/api.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def api_call(
191191
print("Please specify a valid method type! Must be one of: ", METHOD_TYPES)
192192
return Response()
193193

194-
if method not in ["get", "delete"] and data is None:
194+
if method not in ["get", "delete"] and not data:
195195
print(
196196
"Paramater mismatch! If calling anything other than get or delete provide data!"
197197
)

cloudendure/aws.py

Whitespace-only changes.

cloudendure/cloudendure.py

+196-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""Define the CloudEndure main entry logic."""
44
from __future__ import annotations
55

6+
import base64
67
import datetime
78
import json
89
import os
@@ -16,6 +17,7 @@
1617

1718
from .api import CloudEndureAPI
1819
from .config import CloudEndureConfig
20+
from .constants import get_aws_regions
1921
from .events import Event, EventHandler
2022
from .exceptions import CloudEndureHTTPException, CloudEndureMisconfigured
2123
from .templates import TerraformTemplate
@@ -79,6 +81,15 @@ def __init__(
7981
"Failed to authenticate with CloudEndure! Please check your credentials and try again!"
8082
)
8183

84+
def _get_role_credentials(self, name: str, role: str) -> Dict[str, Any]:
85+
_sts_client: boto_client = boto3.client("sts")
86+
87+
print(f"Assuming role: {role}")
88+
assumed_role: Dict[str, Any] = _sts_client.assume_role(
89+
RoleArn=self.destination_role, RoleSessionName=name
90+
)
91+
return assumed_role.get("Credentials", {})
92+
8293
def get_project_id(self, project_name: str = "") -> str:
8394
"""Get the associated CloudEndure project ID by project_name.
8495
@@ -128,15 +139,194 @@ def get_project_id(self, project_name: str = "") -> str:
128139
return ""
129140
return project_id
130141

131-
def _get_role_credentials(self, name: str, role: str) -> Dict[str, Any]:
132-
_sts_client: boto_client = boto3.client("sts")
142+
def get_cloud(self, cloud_type: str = "") -> str:
143+
"""Get the ID for the specified cloud type."""
144+
if not cloud_type:
145+
cloud_type = self.config.active_config.get("cloud_type", "AWS")
133146

134-
print(f"Assuming role: {role}")
135-
assumed_role: Dict[str, Any] = _sts_client.assume_role(
136-
RoleArn=self.destination_role, RoleSessionName=name
147+
clouds_result: Response = self.api.api_call("clouds")
148+
for cloud in json.loads(clouds_result.content)["items"]:
149+
if cloud.get("name", "") == cloud_type:
150+
return cloud.get("id", "")
151+
return ""
152+
153+
def create_cloud_credentials(
154+
self, access_key: str = "", secret_key: str = ""
155+
) -> str:
156+
"""Create a new CloudEndure project.
157+
158+
Args:
159+
project_name (str): The name of the CloudEndure project to be created.
160+
161+
Returns:
162+
str: The newly created CloudEndure project ID.
163+
164+
"""
165+
_encoded_private_key = base64.b64encode(secret_key.encode())
166+
_project = {
167+
"accountIdentifier": "",
168+
"publicKey": access_key,
169+
"privateKey": _encoded_private_key,
170+
"cloudId": self.get_cloud(),
171+
}
172+
173+
cloud_cred_result: Response = self.api.api_call(
174+
"cloudCredentials", method="post", data=_project
175+
)
176+
if cloud_cred_result.status_code != 201:
177+
print(
178+
f"Failed to create the new cloud credentials: ({access_key}): "
179+
f"{cloud_cred_result.status_code} {cloud_cred_result.content}"
180+
)
181+
return ""
182+
print(f"Cloud Credentials: ({access_key}) was created successfully!")
183+
return json.loads(cloud_cred_result.content).get("id", "")
184+
185+
def create_repl_config(self, region: str = "", cloud_cred_id: str = ""):
186+
"""Create a CloudEndure project replication configuration.
187+
188+
Args:
189+
project_name (str): The name of the CloudEndure project to get replication configurations for.
190+
191+
Returns:
192+
list of dict: The CloudEndure replication configuration dictionary mapping.
193+
194+
"""
195+
regions = get_aws_regions()
196+
payload = {
197+
# "archivingEnabled": False,
198+
"bandwidthThrottling": 0,
199+
# "cloudCredentials": "",
200+
# "id": "",
201+
# "objectStorageLocation": "",
202+
# "proxyUrl": "",
203+
# "region": "",
204+
"replicationServerType": "Default",
205+
"replicationTags": [],
206+
# "replicatorSecurityGroupIDs": [],
207+
# "subnetHostProject": "",
208+
# "subnetId": "",
209+
"useDedicatedServer": False,
210+
"useLowCostDisks": False,
211+
"usePrivateIp": True,
212+
"volumeEncryptionAllowed": False,
213+
# "volumeEncryptionKey": ""
214+
}
215+
216+
credentials_response: Response = self.api.api_call(
217+
f"cloudCredentials/{cloud_cred_id}/regions"
218+
)
219+
for _region in json.loads(credentials_response.content).get("items", []):
220+
if _region["name"] == regions.get(region, "N/A"):
221+
payload["region"] = _region["id"]
222+
223+
print(payload)
224+
repl_result: Response = self.api.api_call(
225+
f"projects/{self.project_id}/replicationConfigurations",
226+
method="post",
227+
data=payload,
228+
)
229+
if repl_result.status_code != 201:
230+
print(
231+
f"Failed to create replication configuration {repl_result.status_code} {repl_result.content}"
232+
)
233+
return ""
234+
print("Replication configuration was created successfully")
235+
return json.loads(repl_result.content).get("id", "")
236+
237+
def get_repl_configs(self) -> List[Any]:
238+
"""Get a CloudEndure project's replication configurations.
239+
240+
Args:
241+
project_name (str): The name of the CloudEndure project to get replication configurations for.
242+
243+
Returns:
244+
list of dict: The CloudEndure replication configuration dictionary mapping.
245+
246+
"""
247+
print(
248+
f"Fetching Replication Configuration - Project Name: ({self.project_name})"
249+
)
250+
repl_config_results: Response = self.api.api_call(
251+
f"projects/{self.project_id}/replicationConfigurations"
252+
)
253+
254+
if repl_config_results.status_code != 200:
255+
print(
256+
f"Failed to fetch replication configurations for ({self.project_name}): "
257+
f"{repl_config_results.status_code} {repl_config_results.content}"
258+
)
259+
print(repl_config_results.text)
260+
return []
261+
262+
repl_configs = json.loads(repl_config_results.content).get("items", [])
263+
print(
264+
f"Successfully fetched replication configurations for project: {self.project_name}"
265+
)
266+
return repl_configs
267+
268+
def create_project(self, project_name: str) -> str:
269+
"""Create a new CloudEndure project.
270+
271+
Args:
272+
project_name (str): The name of the CloudEndure project to be created.
273+
274+
Returns:
275+
str: The newly created CloudEndure project ID.
276+
277+
"""
278+
project = {
279+
"licensesIDs": [],
280+
"name": project_name,
281+
"targetCloudId": self.get_cloud(),
282+
"type": "MIGRATION",
283+
}
284+
licenses_result: Response = self.api.api_call("licenses")
285+
286+
for license in json.loads(licenses_result.content).get("items", []):
287+
license_id = license.get("id", "")
288+
if license_id:
289+
print(license_id)
290+
project["licensesIDs"].append(license_id)
291+
292+
projects_result: Response = self.api.api_call(
293+
"projects", method="post", data=project
137294
)
295+
if projects_result.status_code != 201:
296+
print(
297+
f"Failed to create the new project ({self.project_name}): "
298+
f"{projects_result.status_code} {projects_result.content}"
299+
)
300+
return ""
301+
print(f"Project: ({self.project_name}) was created successfully!")
302+
return json.loads(projects_result.content).get("id", "")
303+
304+
def update_project(self, project_data: Dict[str, Any] = None) -> bool:
305+
"""Update a CloudEndure project.
306+
307+
Args:
308+
project_name (str): The name of the CloudEndure project to be updated.
309+
project_data (dict): The project payload to be used to update the project.
310+
Defaults to the current project state.
138311
139-
return assumed_role.get("Credentials")
312+
Returns:
313+
bool: Whether or not the project has been updated.
314+
315+
"""
316+
print(f"Updating Project - Name: ({self.project_name})")
317+
projects_result: Response = self.api.api_call(
318+
f"projects/{self.project_id}", method="patch", data=project_data
319+
)
320+
321+
if projects_result.status_code != 200:
322+
print(
323+
f"Failed to update the project ({self.project_name}): "
324+
f"{projects_result.status_code} {projects_result.content}"
325+
)
326+
print(projects_result.text)
327+
return False
328+
print("Project was updated successfully")
329+
return True
140330

141331
def check(self) -> bool:
142332
"""Check the status of machines in the provided project."""

cloudendure/constants.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- coding: utf-8 -*-
2+
"""Define the CloudEndure reusable variables."""
3+
4+
from typing import Dict
5+
6+
7+
def get_aws_regions() -> Dict[str, str]:
8+
"""Return a hardcoded dictionary of AWS regions.
9+
10+
Returns:
11+
dict: A mapping of AWS regions.
12+
13+
"""
14+
return {
15+
"US East (Ohio)": "us-east-2",
16+
"US East (N. Virginia)": "us-east-1",
17+
"US West (N. California)": "us-west-1",
18+
"US West (Oregon)": "us-west-2",
19+
"Asia Pacific (Hong Kong)": "ap-east-1",
20+
"Asia Pacific (Mumbai)": "ap-south-1",
21+
"Asia Pacific (Osaka-Local)": "ap-northeast-3",
22+
"Asia Pacific (Seoul)": "ap-northeast-2",
23+
"Asia Pacific (Singapore)": "ap-southeast-1",
24+
"Asia Pacific (Sydney)": "ap-southeast-2",
25+
"Asia Pacific (Tokyo)": "ap-northeast-1",
26+
"Canada (Central)": "ca-central-1",
27+
"China (Beijing)": "cn-north-1",
28+
"China (Ningxia)": "cn-northwest-1",
29+
"EU (Frankfurt)": "eu-central-1",
30+
"EU (Ireland)": "eu-west-1",
31+
"EU (London)": "eu-west-2",
32+
"EU (Paris)": "eu-west-3",
33+
"EU (Stockholm)": "eu-north-1",
34+
"Middle East (Bahrain)": "me-south-1",
35+
"South America (Sao Paulo)": "sa-east-1",
36+
"AWS GovCloud (US-East)": "us-gov-east-1",
37+
"AWS GovCloud (US-West)": "us-gov-west-1",
38+
}

pydocmd.yml

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ generate:
99
- cloudendure.cloudendure++
1010
- code/cloudendure/config.md:
1111
- cloudendure.config++
12+
- code/cloudendure/constants.md:
13+
- cloudendure.constants++
1214
- code/cloudendure/events.md:
1315
- cloudendure.events++
1416
- code/cloudendure/exceptions.md:
@@ -36,6 +38,7 @@ pages:
3638
- API: code/cloudendure/api.md
3739
- Main: code/cloudendure/cloudendure.md
3840
- Config: code/cloudendure/config.md
41+
- Constants: code/cloudendure/constants.md
3942
- Events: code/cloudendure/events.md
4043
- Exceptions: code/cloudendure/exceptions.md
4144
- Models: code/cloudendure/models.md

0 commit comments

Comments
 (0)