diff --git a/pyproject.toml b/pyproject.toml index fad3d93..b7a5aa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sparecores-runner" -version = "0.0.19" +version = "0.0.20" requires-python = ">= 3.9" dependencies = [ "click", diff --git a/src/sc_runner/cli.py b/src/sc_runner/cli.py index 4e24efe..2a7e54d 100644 --- a/src/sc_runner/cli.py +++ b/src/sc_runner/cli.py @@ -45,6 +45,16 @@ def destroy(**kwargs): pass +@add_click_opts(runner.pulumi_stack) +@cli.group() +def destroy_stack(**kwargs): + """ + Destroy the underlying Pulumi stack. + Useful if the cloud resources were deleted and the normal destroy command fails. + """ + pass + + @add_click_opts(runner.pulumi_stack) @cli.group() def cancel(**kwargs): @@ -70,6 +80,13 @@ def destroy_resources(ctx, **kwargs): vendor = ctx.command.name runner.destroy(vendor, pulumi_opts, kwargs) + @destroy_stack.command(name=vendor) + @click.pass_context + def destroy_stack_cmd(ctx, **kwargs): + pulumi_opts = ctx.parent.params + vendor = ctx.command.name + runner.destroy_stack(vendor, pulumi_opts, kwargs) + @cancel.command(name=vendor) @click.pass_context def cancel_resources(ctx, **kwargs): @@ -80,6 +97,7 @@ def cancel_resources(ctx, **kwargs): # add click options from the resource method's annotated argument list add_click_opts(getattr(resources, f"{resources.PREFIX}{vendor}"))(create_resources) add_click_opts(getattr(resources, f"{resources.PREFIX}{vendor}"))(destroy_resources) + add_click_opts(getattr(resources, f"{resources.PREFIX}{vendor}"))(destroy_stack_cmd) add_click_opts(getattr(resources, f"{resources.PREFIX}{vendor}"))(cancel_resources) diff --git a/src/sc_runner/runner.py b/src/sc_runner/runner.py index 0cc591d..da9bc6c 100644 --- a/src/sc_runner/runner.py +++ b/src/sc_runner/runner.py @@ -66,6 +66,16 @@ def destroy(vendor, pulumi_opts, resource_opts): stack.up(on_output=print) +def destroy_stack(vendor, pulumi_opts, resource_opts): + resource_f = getattr(resources, f"{resources.PREFIX}{vendor}") + if not pulumi_opts.get("stack_name"): + pulumi_opts["stack_name"] = get_stack_name(vendor, resource_f, resource_opts) + stack = pulumi_stack(lambda: None, **pulumi_opts) + stack.refresh(on_output=print) + stack.destroy(on_output=print) + stack.workspace.remove_stack(stack.name) + + def cancel(vendor, pulumi_opts, resource_opts): resource_f = getattr(resources, f"{resources.PREFIX}{vendor}") if not pulumi_opts.get("stack_name"):