2018-11-22 18:31:59 +00:00
|
|
|
import logging
|
2019-07-28 13:02:18 +00:00
|
|
|
import pprint
|
2024-12-03 12:49:16 +00:00
|
|
|
import pexpect
|
|
|
|
import pulumi
|
|
|
|
import tempfile
|
2018-11-22 18:31:59 +00:00
|
|
|
|
2024-12-03 12:49:16 +00:00
|
|
|
import rich.table
|
|
|
|
import rich.console
|
|
|
|
|
|
|
|
from .connection import BotoConnection
|
2019-03-06 19:57:31 +00:00
|
|
|
from .utils import dict_merge
|
|
|
|
from .jinja import read_config_file
|
2018-11-22 18:31:59 +00:00
|
|
|
from .stack import Stack
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class StackGroup(object):
|
|
|
|
def __init__(self, path, ctx):
|
|
|
|
self.name = None
|
|
|
|
self.ctx = ctx
|
|
|
|
self.path = path
|
2022-02-22 10:04:29 +00:00
|
|
|
self.rel_path = path.relative_to(ctx["config_path"])
|
2018-11-22 18:31:59 +00:00
|
|
|
self.config = {}
|
|
|
|
self.sgs = []
|
|
|
|
self.stacks = []
|
|
|
|
|
2022-02-22 10:04:29 +00:00
|
|
|
if self.rel_path == ".":
|
|
|
|
self.rel_path = ""
|
2018-11-22 18:31:59 +00:00
|
|
|
|
|
|
|
def dump_config(self):
|
|
|
|
for sg in self.sgs:
|
|
|
|
sg.dump_config()
|
|
|
|
|
2024-12-03 12:49:16 +00:00
|
|
|
logger.info(
|
2022-02-22 10:04:29 +00:00
|
|
|
"StackGroup {}: {}".format(self.rel_path, pprint.pformat(self.config))
|
|
|
|
)
|
2018-11-22 18:31:59 +00:00
|
|
|
|
|
|
|
for s in self.stacks:
|
|
|
|
s.dump_config()
|
|
|
|
|
2022-07-04 14:15:14 +00:00
|
|
|
def read_config(self, parent_config={}, loadStacks=True):
|
2019-12-09 13:32:39 +00:00
|
|
|
if not self.path.is_dir():
|
2018-11-22 18:31:59 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
# First read config.yaml if present
|
2022-02-22 10:04:29 +00:00
|
|
|
_config = read_config_file(
|
|
|
|
self.path.joinpath("config.yaml"), parent_config.get("variables", {})
|
|
|
|
)
|
2018-11-22 18:31:59 +00:00
|
|
|
|
|
|
|
# Stack Group name if not explicit via config is derived from subfolder, or in case of root object the parent folder
|
|
|
|
if "stackgroupname" in _config:
|
|
|
|
self.name = _config["stackgroupname"]
|
|
|
|
elif not self.name:
|
2019-12-09 13:32:39 +00:00
|
|
|
self.name = self.path.stem
|
2018-11-22 18:31:59 +00:00
|
|
|
|
|
|
|
# Merge config with parent config
|
2019-07-27 22:30:03 +00:00
|
|
|
self.config = dict_merge(parent_config, _config)
|
2022-02-22 10:04:29 +00:00
|
|
|
stackname_prefix = self.config.get("stacknameprefix", "")
|
2018-11-22 18:31:59 +00:00
|
|
|
|
2022-07-04 14:15:14 +00:00
|
|
|
# profile and region need special treatment due to cmd line overwrite option
|
|
|
|
if self.ctx["region"]:
|
|
|
|
self.config["region"] = self.ctx["region"]
|
|
|
|
|
|
|
|
if self.ctx["profile"]:
|
|
|
|
self.config["profile"] = self.ctx["profile"]
|
|
|
|
|
2019-01-21 10:43:36 +00:00
|
|
|
logger.debug("StackGroup {} added.".format(self.name))
|
2018-11-22 18:31:59 +00:00
|
|
|
|
|
|
|
# Add stacks
|
2022-07-04 14:15:14 +00:00
|
|
|
if loadStacks:
|
|
|
|
stacks = [
|
|
|
|
s for s in self.path.glob("*.yaml") if not s.name == "config.yaml"
|
|
|
|
]
|
|
|
|
for stack_path in stacks:
|
|
|
|
stackname = stack_path.name.split(".")[0]
|
|
|
|
template = stackname
|
|
|
|
if stackname_prefix:
|
|
|
|
stackname = stackname_prefix + stackname
|
|
|
|
|
|
|
|
new_stack = Stack(
|
|
|
|
name=stackname,
|
|
|
|
template=template,
|
|
|
|
path=stack_path,
|
|
|
|
rel_path=str(self.rel_path),
|
|
|
|
ctx=self.ctx,
|
|
|
|
)
|
|
|
|
new_stack.read_config(self.config)
|
|
|
|
self.stacks.append(new_stack)
|
2018-11-22 18:31:59 +00:00
|
|
|
|
|
|
|
# Create StackGroups recursively
|
2019-12-09 13:32:39 +00:00
|
|
|
for sub_group in [s for s in self.path.iterdir() if s.is_dir()]:
|
2018-11-22 18:31:59 +00:00
|
|
|
sg = StackGroup(sub_group, self.ctx)
|
2022-07-04 14:15:14 +00:00
|
|
|
sg.read_config(self.config, loadStacks=loadStacks)
|
2018-11-22 18:31:59 +00:00
|
|
|
|
|
|
|
self.sgs.append(sg)
|
|
|
|
|
2022-02-22 10:04:29 +00:00
|
|
|
def get_stacks(self, name=None, recursive=True, match_by="name"):
|
|
|
|
"""Returns [stack] matching stack_name or [all]"""
|
2018-11-22 18:31:59 +00:00
|
|
|
stacks = []
|
|
|
|
if name:
|
|
|
|
logger.debug("Looking for stack {} in group {}".format(name, self.name))
|
|
|
|
|
|
|
|
for s in self.stacks:
|
2019-12-09 13:32:39 +00:00
|
|
|
if name:
|
2022-02-22 10:04:29 +00:00
|
|
|
if match_by == "name" and s.stackname != name:
|
2019-12-09 13:32:39 +00:00
|
|
|
continue
|
|
|
|
|
2022-02-22 10:04:29 +00:00
|
|
|
if match_by == "path" and not s.path.match(name):
|
2019-12-09 13:32:39 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
if self.rel_path:
|
2022-02-22 10:04:29 +00:00
|
|
|
logger.debug(
|
|
|
|
"Found stack {} in group {}".format(s.stackname, self.rel_path)
|
|
|
|
)
|
2019-12-09 13:32:39 +00:00
|
|
|
else:
|
|
|
|
logger.debug("Found stack {}".format(s.stackname))
|
|
|
|
stacks.append(s)
|
2018-11-22 18:31:59 +00:00
|
|
|
|
|
|
|
if recursive:
|
|
|
|
for sg in self.sgs:
|
|
|
|
s = sg.get_stacks(name, recursive, match_by)
|
|
|
|
if s:
|
2019-02-07 15:36:16 +00:00
|
|
|
stacks = stacks + s
|
2018-11-22 18:31:59 +00:00
|
|
|
|
|
|
|
return stacks
|
|
|
|
|
2022-07-04 14:15:14 +00:00
|
|
|
def get_stackgroup(self, name=None, match_by="path"):
|
2022-02-22 10:04:29 +00:00
|
|
|
"""Returns stack group matching stackgroup_name or all if None"""
|
2022-07-04 14:15:14 +00:00
|
|
|
if self.path.match(name):
|
2018-11-22 18:31:59 +00:00
|
|
|
logger.debug("Found stack_group {}".format(self.name))
|
|
|
|
return self
|
|
|
|
|
2022-07-04 14:15:14 +00:00
|
|
|
if name and name != "config":
|
2022-02-22 10:04:29 +00:00
|
|
|
logger.debug(
|
|
|
|
"Looking for stack_group {} in group {}".format(name, self.name)
|
|
|
|
)
|
2018-11-22 18:31:59 +00:00
|
|
|
|
2022-07-04 14:15:14 +00:00
|
|
|
for sg in self.sgs:
|
|
|
|
s = sg.get_stackgroup(name, match_by)
|
|
|
|
if s:
|
|
|
|
return s
|
2018-11-22 18:31:59 +00:00
|
|
|
|
|
|
|
return None
|
2024-12-03 12:49:16 +00:00
|
|
|
|
|
|
|
def wrap(self, cmd):
|
|
|
|
"""
|
|
|
|
Set AWS environment based on profile before executing a custom command, eg. steampipe
|
|
|
|
"""
|
|
|
|
|
|
|
|
profile = self.config.get("profile", "default")
|
|
|
|
region = self.config.get("region", "global")
|
|
|
|
|
|
|
|
connection_manager = BotoConnection(profile, region)
|
|
|
|
connection_manager.exportProfileEnv()
|
|
|
|
|
|
|
|
child = pexpect.spawn(cmd)
|
|
|
|
child.interact()
|
|
|
|
|
|
|
|
def list_stacks(self):
|
|
|
|
project_name = self.config["parameters"]["Conglomerate"]
|
|
|
|
pulumi_backend = "{}/{}/{}".format(self.config["pulumi"]["backend"], project_name, self.config["region"])
|
|
|
|
|
|
|
|
project_settings = pulumi.automation.ProjectSettings(
|
|
|
|
name=project_name, runtime="python", backend=pulumi.automation.ProjectBackend(url=pulumi_backend)
|
|
|
|
)
|
|
|
|
|
|
|
|
work_dir = tempfile.mkdtemp(
|
|
|
|
dir=tempfile.gettempdir(), prefix="cloudbender-"
|
|
|
|
)
|
|
|
|
|
|
|
|
# AWS setup
|
|
|
|
profile = self.config.get("profile", "default")
|
|
|
|
region = self.config.get("region", "global")
|
|
|
|
|
|
|
|
connection_manager = BotoConnection(profile, region)
|
|
|
|
connection_manager.exportProfileEnv()
|
|
|
|
|
|
|
|
pulumi_workspace = pulumi.automation.LocalWorkspace(
|
|
|
|
work_dir=work_dir,
|
|
|
|
project_settings=project_settings
|
|
|
|
)
|
|
|
|
|
|
|
|
stacks = pulumi_workspace.list_stacks()
|
|
|
|
|
|
|
|
table = rich.table.Table(title="Pulumi stacks")
|
|
|
|
table.add_column("Name")
|
|
|
|
table.add_column("Last Update")
|
|
|
|
table.add_column("Resources")
|
|
|
|
|
|
|
|
for s in stacks:
|
|
|
|
table.add_row(s.name, str(s.last_update), str(s.resource_count))
|
|
|
|
|
|
|
|
console = rich.console.Console()
|
|
|
|
console.print(table)
|