Skip to content
Merged
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
4 changes: 2 additions & 2 deletions ci/builder/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ COPY nodesource.asc .

RUN gpg --dearmor < nodesource.asc > /etc/apt/keyrings/nodesource.gpg \
&& rm nodesource.asc \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs \
&& apt-get clean \
Expand Down Expand Up @@ -496,7 +496,7 @@ COPY nodesource.asc .

RUN gpg --dearmor < nodesource.asc > /etc/apt/keyrings/nodesource.gpg \
&& rm nodesource.asc \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs \
&& apt-get clean \
Expand Down
6 changes: 3 additions & 3 deletions misc/completions/bash/_scratch
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ _shtab_scratch_subparsers=('login' 'create' 'list' 'ls' 'ssh' 'sftp' 'destroy' '

_shtab_scratch_option_strings=('-h' '--help')
_shtab_scratch_login_option_strings=('-h' '--help')
_shtab_scratch_create_option_strings=('-h' '--help' '--key-name' '--security-group-name' '--extra-tags' '--instance-profile' '--output-format' '--git-rev' '--ssh' '--max-age-days')
_shtab_scratch_create_option_strings=('-h' '--help' '--key-name' '--security-group-name' '--extra-tags' '--instance-profile' '--output-format' '--git-rev' '--ssh' '--instance-type' '--size-gb' '--max-age-days')
_shtab_scratch_list_option_strings=('-h' '--help' '--all' '--output-format')
_shtab_scratch_ls_option_strings=('-h' '--help' '--all' '--output-format')
_shtab_scratch_ssh_option_strings=('-h' '--help')
Expand All @@ -14,8 +14,8 @@ _shtab_scratch_rm_option_strings=('-h' '--help' '--all-mine' '-y' '--yes' '--out
_shtab_scratch_push_option_strings=('-h' '--help' '--rev')
_shtab_scratch_completion_option_strings=('-h' '--help')
_shtab_scratch_forward_option_strings=('-h' '--help')
_shtab_scratch_go_option_strings=('-h' '--help' '--max-age-days')
_shtab_scratch_claude_option_strings=('-h' '--help' '--max-age-days')
_shtab_scratch_go_option_strings=('-h' '--help' '--instance-type' '--size-gb' '--max-age-days')
_shtab_scratch_claude_option_strings=('-h' '--help' '--instance-type' '--size-gb' '--max-age-days')



Expand Down
6 changes: 6 additions & 0 deletions misc/completions/zsh/_scratch
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ _shtab_scratch_defaults_added=0

_shtab_scratch_claude_options=(
"(- : *)"{-h,--help}"[show this help message and exit]"
"--instance-type[Override the EC2 instance type. The AMI is derived from its architecture. May be used without a machine config.]:instance_type:"
"--size-gb[Override the root volume size in GiB.]:size_gb:"
"--max-age-days[Maximum age for scratch instance in days. Defaults to 1.5]:max_age_days:"
":Machine config name from misc\/scratch (e.g. dev-box).:"
)
Expand All @@ -56,6 +58,8 @@ _shtab_scratch_create_options=(
"--output-format[]:output_format:(table csv)"
"--git-rev[Git revision of \`materialize\` codebase to push to scratch instance. Defaults to \`HEAD\`]:git_rev:"
"--ssh[ssh into the machine after the launch script is run. Only works if a single instance was started]"
"--instance-type[Override the EC2 instance type. The AMI is derived from its architecture. May be used without a machine config.]:instance_type:"
"--size-gb[Override the root volume size in GiB.]:size_gb:"
"--max-age-days[Maximum age for scratch instance in days. Defaults to 1.5]:max_age_days:"
":Use a config from \{machine\}.json in \`misc\/scratch\`. Hint\: \`dev-box\` is a good starter\!:"
)
Expand Down Expand Up @@ -85,6 +89,8 @@ _shtab_scratch_forward_defaults_added=0

_shtab_scratch_go_options=(
"(- : *)"{-h,--help}"[show this help message and exit]"
"--instance-type[Override the EC2 instance type. The AMI is derived from its architecture. May be used without a machine config.]:instance_type:"
"--size-gb[Override the root volume size in GiB.]:size_gb:"
"--max-age-days[Maximum age for scratch instance in days. Defaults to 1.5]:max_age_days:"
":Machine config name from misc\/scratch (e.g. dev-box). Used only when creating a new instance.:"
)
Expand Down
43 changes: 33 additions & 10 deletions misc/python/materialize/cli/scratch/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
import datetime

from materialize.cli.scratch import check_required_vars, list_all_instances
from materialize.cli.scratch.create import MAX_AGE_DAYS, multi_json, pick_machine
from materialize.cli.scratch.create import (
MAX_AGE_DAYS,
load_machine_descs,
pick_machine,
)
from materialize.scratch import (
MZ_ROOT,
MachineDesc,
launch_cluster,
mssh,
print_instances,
Expand All @@ -32,6 +34,19 @@ def configure_parser(parser: argparse.ArgumentParser) -> None:
default=None,
help="Machine config name from misc/scratch (e.g. dev-box).",
)
parser.add_argument(
"--instance-type",
type=str,
help=(
"Override the EC2 instance type. The AMI is derived from its "
"architecture. May be used without a machine config."
),
)
parser.add_argument(
"--size-gb",
type=int,
help="Override the root volume size in GiB.",
)
parser.add_argument(
"--max-age-days",
type=float,
Expand All @@ -41,10 +56,18 @@ def configure_parser(parser: argparse.ArgumentParser) -> None:


def run(args: argparse.Namespace) -> None:
machine = args.machine or pick_machine()

with open(MZ_ROOT / "misc" / "scratch" / f"{machine}.json") as f:
descs = [MachineDesc.model_validate(obj) for obj in multi_json(f.read())]
# Resolve the machine config name eagerly so it can be shown below, unless
# the user supplied a bare instance type instead.
machine = args.machine
if machine is None and args.instance_type is None:
machine = pick_machine()
label = machine or args.instance_type

descs = load_machine_descs(
machine,
instance_type=args.instance_type,
size_gb=args.size_gb,
)

if len(descs) != 1:
raise RuntimeError(
Expand All @@ -61,7 +84,7 @@ def run(args: argparse.Namespace) -> None:

if active:
print_instances(active, numbered=True)
print(f" {len(active) + 1}) Create new instance ({machine})")
print(f" {len(active) + 1}) Create new instance ({label})")
while True:
choice = input("Select an instance or create new [#]: ").strip()
try:
Expand All @@ -79,13 +102,13 @@ def run(args: argparse.Namespace) -> None:
instance = None

if instance is None:
say(f"Creating from {machine}...")
say(f"Creating from {label}...")
max_age = datetime.timedelta(days=args.max_age_days)
extra_tags = {"LaunchedBy": whoami()}
instances = launch_cluster(
descs,
extra_tags=extra_tags,
delete_after=datetime.datetime.utcnow() + max_age,
delete_after=datetime.datetime.now(datetime.timezone.utc) + max_age,
)

print("Launched:")
Expand Down
83 changes: 65 additions & 18 deletions misc/python/materialize/cli/scratch/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
launch_cluster,
mssh,
print_instances,
render_table,
whoami,
)

Expand All @@ -36,8 +37,6 @@ def _natsort_key(s: str) -> list:

def pick_machine() -> str:
"""Show available machine configs and let the user pick one."""
from prettytable import PrettyTable

scratch_dir = MZ_ROOT / "misc" / "scratch"
configs: list[tuple[str, str, str]] = []
for f in sorted(scratch_dir.glob("*.json"), key=lambda f: _natsort_key(f.stem)):
Expand All @@ -46,12 +45,13 @@ def pick_machine() -> str:
for d in descs:
configs.append((f.stem, d.instance_type, d.name))

pt = PrettyTable()
pt.field_names = ["#", "Config", "Instance Type", "Name"]
pt.align = "l"
for i, (stem, instance_type, name) in enumerate(configs, 1):
pt.add_row([i, stem, instance_type, name])
print(pt)
render_table(
["#", "CONFIG", "INSTANCE TYPE", "NAME"],
[
[str(i), stem, instance_type, name]
for i, (stem, instance_type, name) in enumerate(configs, 1)
],
)

while True:
choice = input("Select a machine config [#]: ").strip()
Expand Down Expand Up @@ -92,6 +92,44 @@ def multi_json(s: str) -> list[dict[Any, Any]]:
return result


def load_machine_descs(
machine: str | None,
*,
instance_type: str | None = None,
size_gb: int | None = None,
) -> list[MachineDesc]:
"""Load machine configs, applying optional overrides.

With no `machine` but an explicit `instance_type`, synthesize a single
default config so callers can launch an arbitrary instance type without a
preset file. With no `machine` and no `instance_type`, prompt for one."""
if machine is None and instance_type is not None:
desc = MachineDesc(name=instance_type, instance_type=instance_type)
if size_gb is not None:
desc.size_gb = size_gb
return [desc]

if machine is None:
machine = pick_machine()

with open(MZ_ROOT / "misc" / "scratch" / f"{machine}.json") as f:
print(f"Reading machine configs from {f.name}")
descs = [MachineDesc.model_validate(obj) for obj in multi_json(f.read())]

if instance_type is not None:
if len(descs) != 1:
raise RuntimeError(
f"--instance-type requires a single-machine config, got {len(descs)}"
)
descs[0].instance_type = instance_type
# An explicit type override invalidates any pinned AMI; re-derive it.
descs[0].ami = None
if size_gb is not None:
for d in descs:
d.size_gb = size_gb
return descs


def configure_parser(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"--key-name", type=str, required=False, help="Optional EC2 Key Pair name"
Expand Down Expand Up @@ -139,6 +177,19 @@ def configure_parser(parser: argparse.ArgumentParser) -> None:
"Hint: `dev-box` is a good starter!"
),
)
parser.add_argument(
"--instance-type",
type=str,
help=(
"Override the EC2 instance type. The AMI is derived from its "
"architecture. May be used without a machine config."
),
)
parser.add_argument(
"--size-gb",
type=int,
help="Override the root volume size in GiB.",
)
parser.add_argument(
"--max-age-days",
type=float,
Expand All @@ -158,15 +209,11 @@ def run(args: argparse.Namespace) -> None:
"extra-tags must be a JSON dictionary of strings to strings"
)

if args.machine:
with open(MZ_ROOT / "misc" / "scratch" / f"{args.machine}.json") as f:
print(f"Reading machine configs from {f.name}")
descs = [MachineDesc.model_validate(obj) for obj in multi_json(f.read())]
else:
args.machine = pick_machine()
with open(MZ_ROOT / "misc" / "scratch" / f"{args.machine}.json") as f:
print(f"Reading machine configs from {f.name}")
descs = [MachineDesc.model_validate(obj) for obj in multi_json(f.read())]
descs = load_machine_descs(
args.machine,
instance_type=args.instance_type,
size_gb=args.size_gb,
)

check_required_vars()
extra_tags["LaunchedBy"] = whoami()
Expand All @@ -184,7 +231,7 @@ def run(args: argparse.Namespace) -> None:
security_group_name=args.security_group_name,
instance_profile=args.instance_profile,
extra_tags=extra_tags,
delete_after=datetime.datetime.utcnow() + max_age,
delete_after=datetime.datetime.now(datetime.timezone.utc) + max_age,
git_rev=args.git_rev,
extra_env={},
)
Expand Down
33 changes: 25 additions & 8 deletions misc/python/materialize/cli/scratch/go.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@
import datetime

from materialize.cli.scratch import check_required_vars
from materialize.cli.scratch.create import MAX_AGE_DAYS, multi_json
from materialize.cli.scratch.create import MAX_AGE_DAYS, load_machine_descs
from materialize.scratch import (
MZ_ROOT,
MachineDesc,
get_instance,
launch_cluster,
mssh,
Expand All @@ -32,11 +30,26 @@
def configure_parser(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"machine",
nargs="?",
default=None,
help=(
"Machine config name from misc/scratch (e.g. dev-box). "
"Used only when creating a new instance."
),
)
parser.add_argument(
"--instance-type",
type=str,
help=(
"Override the EC2 instance type. The AMI is derived from its "
"architecture. May be used without a machine config."
),
)
parser.add_argument(
"--size-gb",
type=int,
help="Override the root volume size in GiB.",
)
parser.add_argument(
"--max-age-days",
type=float,
Expand All @@ -46,9 +59,12 @@ def configure_parser(parser: argparse.ArgumentParser) -> None:


def run(args: argparse.Namespace) -> None:
# Read the machine config
with open(MZ_ROOT / "misc" / "scratch" / f"{args.machine}.json") as f:
descs = [MachineDesc.model_validate(obj) for obj in multi_json(f.read())]
# Read the machine config, applying any overrides.
descs = load_machine_descs(
args.machine,
instance_type=args.instance_type,
size_gb=args.size_gb,
)

if len(descs) != 1:
raise RuntimeError(
Expand All @@ -69,14 +85,15 @@ def run(args: argparse.Namespace) -> None:
pass

# No existing instance — create one
say(f"No existing instance found, creating from {args.machine}...")
label = args.machine or args.instance_type
say(f"No existing instance found, creating from {label}...")

max_age = datetime.timedelta(days=args.max_age_days)
extra_tags = {"LaunchedBy": whoami()}
instances = launch_cluster(
descs,
extra_tags=extra_tags,
delete_after=datetime.datetime.utcnow() + max_age,
delete_after=datetime.datetime.now(datetime.timezone.utc) + max_age,
)

print("Launched:")
Expand Down
7 changes: 6 additions & 1 deletion misc/python/materialize/cli/scratch/list_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@ def configure_parser(parser: argparse.ArgumentParser) -> None:

def run(args: argparse.Namespace) -> None:
instances = list_all_instances(owners=args.owner or None, all=args.all)
print_instances(instances, args.output_format)
# Show the owner column only when listing instances beyond your own.
print_instances(
instances,
args.output_format,
show_launched_by=args.all or bool(args.owner),
)
Loading
Loading