Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/cfengine_cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
format_policy_fin_fout,
)
from cfengine_cli.utils import UserError
from cfengine_cli.up import validate_config
from cfengine_cli.up import validate_config, spawn_from_config
from cfbs.commands import build_command
from cf_remote.commands import deploy as deploy_command

Expand Down Expand Up @@ -178,12 +178,13 @@ def up(args) -> int:
with open(args.config, "r") as f:
content = yaml.safe_load(f)
except yaml.YAMLError:
raise UserError("'%s' is not a valid yaml config" % args.config)
raise UserError("'%s' is not valid yaml" % args.config)
except FileNotFoundError:
raise UserError("'%s' doesn't exist" % args.config)

validate_config(content)
state = validate_config(content)
if args.validate:
return 0
print("Starting VMs...")

spawn_from_config(state)
return 0
121 changes: 118 additions & 3 deletions src/cfengine_cli/up.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
from pydantic import BaseModel, model_validator, ValidationError, Field
from typing import Union, Literal, Optional, List, Annotated
from functools import reduce
from cf_remote import log

import cfengine_cli.validate as validate
from cfengine_cli.utils import UserError

from cf_remote import log
from cf_remote import commands
from cf_remote import utils
from cf_remote import paths
from cf_remote import aramid

import itertools
import time

# Forces pydantic to throw validation error if config contains unknown keys
class NoExtra(BaseModel, extra="forbid"):
Expand All @@ -18,7 +25,8 @@ class Config(NoExtra):

class AWSConfig(Config):
image: str
size: Literal["micro", "xlarge"] = "micro"
size: Optional[Literal["micro", "xlarge"]] = None
region: Optional[str] = None

@model_validator(mode="after")
def check_aws_config(self):
Expand Down Expand Up @@ -49,7 +57,8 @@ class GCPConfig(Config):
image: str # There is no list of available GCP platforms to validate against yet
network: Optional[str] = None
public_ip: bool = True
size: str = "n1-standard-1"
size: Optional[str] = None
region: Optional[str] = None


class AWSProvider(Config):
Expand Down Expand Up @@ -194,6 +203,10 @@ def validate_config(content):
raise UserError("Missing 'groups' key in spawn config")

groups = content["groups"]

if groups is None:
return {}

templates = content.get("templates")
if templates:
_resolve_templates(groups, templates)
Expand All @@ -219,3 +232,105 @@ def validate_config(content):
f"{err['msg']}. Input '{err['input']}' at location '{err['loc']}'"
)
raise UserError("\n".join(msgs))
return state

def three_dots():
for _ in range(3):
print(".", end="", flush=True)
time.sleep(0.3)

def spawn_from_config(state):

data = utils.read_json(paths.CLOUD_STATE_FPATH)
if data is None:
data = {}

spawn = []
destroy = []

for old, new in itertools.zip_longest(data.keys(), state.keys()):

if old is not None:
if old[1:] not in state:
destroy.append(old)
print("<<< {}".format(old[1:]))

if new is not None:
if "@{}".format(new) not in data:
spawn.append(new)
print(">>> {}".format(new))

for k in spawn:
config = state[k].config

match config.source.mode:
case "spawn":
args = {
"group_name": k,
"count": config.source.count,
"role": config.role,
}

match config.source.spawn.provider:
case "vagrant":
args |= {
"provider": commands.Providers.VAGRANT,
"size": config.source.spawn.vagrant.memory,
"platform": config.source.spawn.vagrant.box,
"vagrant_cpus": config.source.spawn.vagrant.cpus,
"vagrant_sync_folder": config.source.spawn.vagrant.sync_folder,
"vagrant_provision": config.source.spawn.vagrant.provision,
}
case "aws":
args |= {
"provider": commands.Providers.AWS,
"platform": config.source.spawn.aws.image,
"size": config.source.spawn.aws.size,
"region": config.source.spawn.aws.region,
}
case "gcp":
args |= {
"provider": commands.Providers.GCP,
"platform": config.source.spawn.gcp.image,
"network": config.source.spawn.gcp.network,
"public_ip": config.source.spawn.gcp.public_ip,
"size": config.source.spawn.gcp.size,
"region": config.source.spawn.gcp.region,
}

commands.spawn(**args)
case "save":
commands.save(name=k, hosts=config.source.hosts, role=config.role)

for k in destroy:
commands.destroy(k)

data = utils.read_json(paths.CLOUD_STATE_FPATH)
if data is None:
data = {}

if spawn:
print("Waiting for VMs to be accessible", end="")
three_dots()
print()

for k,v in data.items():

if k[1:] not in spawn:
continue

ips = [h_info["public_ips"][0] for h, h_info in v.items() if h != "meta"]
hosts = [aramid.Host(host_name=ip, user="admin") for ip in ips]

while True:
try:
aramid.execute(hosts, "echo", ignore_failed=False, echo=False, echo_cmd=False)
break
except aramid.ExecutionError:
three_dots()
print("\r\033[2K", end="", flush=True)

commands.info(ips)

print("done")

Loading