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
21 changes: 17 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ INSTALL_PATH ?= ~/bin
GIT_SHA := $(shell git log -1 --pretty=format:"%H")
LD_FLAGS := "-X github.com/massdriver-cloud/mass/internal/version.version=dev -X github.com/massdriver-cloud/mass/internal/version.gitSHA=local-dev-${GIT_SHA}"

MAKEFLAGS += --no-print-directory

.DEFAULT_GOAL := install

.PHONY: check
Expand All @@ -13,7 +15,18 @@ clean:

.PHONY: docs
docs: build
./mass docs
@OS="$$(uname -s)"; \
if [ "$$OS" = "Darwin" ]; then \
./bin/mass-darwin-arm64 docs; \
elif [ "$$OS" = "Linux" ]; then \
./bin/mass-linux-amd64 docs; \
elif echo "$$OS" | grep -q -E "^(MINGW|MSYS|Windows)"; then \
./bin/mass-windows-amd64.exe docs; \
else \
echo "Unsupported OS: $$OS"; \
exit 1; \
fi
@echo "docs generated"

.PHONY: test
test:
Expand Down Expand Up @@ -41,15 +54,15 @@ build:

.PHONY: build.macos
build.macos: bin
GOOS=darwin GOARCH=arm64 go build -o bin/mass-darwin-arm64 -ldflags=${LD_FLAGS}
@GOOS=darwin GOARCH=arm64 go build -o bin/mass-darwin-arm64 -ldflags=${LD_FLAGS}

.PHONY: build.linux
build.linux: bin
GOOS=linux GOARCH=amd64 go build -o bin/mass-linux-amd64 -ldflags=${LD_FLAGS}
@GOOS=linux GOARCH=amd64 go build -o bin/mass-linux-amd64 -ldflags=${LD_FLAGS}

.PHONY: build.windows
build.windows: bin
GOOS=windows GOARCH=amd64 go build -o bin/mass-windows-amd64.exe -ldflags=${LD_FLAGS}
@GOOS=windows GOARCH=amd64 go build -o bin/mass-windows-amd64.exe -ldflags=${LD_FLAGS}

.PHONY: install.macos
install.macos: build.macos
Expand Down
49 changes: 49 additions & 0 deletions cmd/deployment.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"bufio"
"bytes"
"context"
"embed"
Expand All @@ -9,12 +10,14 @@ import (
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"text/template"

"github.com/charmbracelet/glamour"
"github.com/massdriver-cloud/mass/docs/helpdocs"
"github.com/massdriver-cloud/mass/internal/cli"
"github.com/massdriver-cloud/mass/internal/prettylogs"
"github.com/massdriver-cloud/massdriver-sdk-go/massdriver"

"github.com/massdriver-cloud/massdriver-sdk-go/massdriver/platform/deployments"
Expand Down Expand Up @@ -64,9 +67,20 @@ func NewCmdDeployment() *cobra.Command {
RunE: runDeploymentLogs,
}

deploymentAbortCmd := &cobra.Command{
Use: "abort <deployment-id>",
Short: "Abort a pending, approved, or running deployment",
Example: `mass deployment abort 12345678-1234-1234-1234-123456789012 --force`,
Long: helpdocs.MustRender("deployment/abort"),
Args: cobra.ExactArgs(1),
RunE: runDeploymentAbort,
}
deploymentAbortCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt")

deploymentCmd.AddCommand(deploymentGetCmd)
deploymentCmd.AddCommand(deploymentListCmd)
deploymentCmd.AddCommand(deploymentLogsCmd)
deploymentCmd.AddCommand(deploymentAbortCmd)

return deploymentCmd
}
Expand Down Expand Up @@ -184,6 +198,41 @@ func signalContext(parent context.Context) (context.Context, context.CancelFunc)
return signal.NotifyContext(parent, syscall.SIGINT, syscall.SIGTERM)
}

func runDeploymentAbort(cmd *cobra.Command, args []string) error {
ctx := context.Background()
deploymentID := args[0]

force, err := cmd.Flags().GetBool("force")
if err != nil {
return err
}
cmd.SilenceUsage = true

if !force {
fmt.Printf("%s: This will abort deployment %s. A running deployment aborted mid-flight leaves any partial infrastructure changes in place, and does not halt execution of the deployment if it is already running.\n", prettylogs.Orange("WARNING"), deploymentID)
fmt.Printf("Type '%s' to confirm abort: ", deploymentID)
reader := bufio.NewReader(os.Stdin)
answer, _ := reader.ReadString('\n')
if strings.TrimSpace(answer) != deploymentID {
fmt.Println("Abort cancelled.")
return nil
}
}

mdClient, err := massdriver.NewClient()
if err != nil {
return fmt.Errorf("error initializing massdriver client: %w", err)
}

aborted, err := mdClient.Deployments.Abort(ctx, deploymentID)
if err != nil {
return err
}

fmt.Printf("✅ Deployment `%s` aborted (status: %s)\n", aborted.ID, aborted.Status)
return nil
}

//nolint:dupl // parallel template-render shape with renderInstance; consolidating would couple unrelated commands
func renderDeployment(deployment *types.Deployment) error {
tmplBytes, err := deploymentTemplates.ReadFile("templates/deployment.get.md.tmpl")
Expand Down
57 changes: 57 additions & 0 deletions cmd/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/massdriver-cloud/mass/internal/cli"
"github.com/massdriver-cloud/mass/internal/commands/instance"
"github.com/massdriver-cloud/mass/internal/files"
"github.com/massdriver-cloud/mass/internal/prettylogs"
"github.com/massdriver-cloud/massdriver-sdk-go/massdriver"

"github.com/charmbracelet/glamour"
Expand Down Expand Up @@ -105,12 +106,24 @@ func NewCmdInstance() *cobra.Command {
RunE: runInstanceList,
}

instanceOrphanCmd := &cobra.Command{
Use: `orphan <project>-<env>-<manifest>`,
Short: "Orphan an instance (reset to INITIALIZED, optionally clearing state locks)",
Example: `mass instance orphan api-prod-db --force`,
Long: helpdocs.MustRender("instance/orphan"),
Args: cobra.ExactArgs(1),
RunE: runInstanceOrphan,
}
instanceOrphanCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt")
instanceOrphanCmd.Flags().Bool("delete-state", false, "Also delete the remote Terraform/OpenTofu state files (irreversible)")

instanceCmd.AddCommand(instanceDeployCmd)
instanceCmd.AddCommand(instanceExportCmd)
instanceCmd.AddCommand(instanceGetCmd)
instanceCmd.AddCommand(instanceListCmd)
instanceCmd.AddCommand(instanceVersionCmd)
instanceCmd.AddCommand(instanceDestroyCmd)
instanceCmd.AddCommand(instanceOrphanCmd)

return instanceCmd
}
Expand Down Expand Up @@ -342,6 +355,50 @@ func runInstanceVersion(cmd *cobra.Command, args []string) error {
return nil
}

func runInstanceOrphan(cmd *cobra.Command, args []string) error {
ctx := context.Background()
name := args[0]

force, err := cmd.Flags().GetBool("force")
if err != nil {
return err
}
deleteState, err := cmd.Flags().GetBool("delete-state")
if err != nil {
return err
}
cmd.SilenceUsage = true

if !force {
if deleteState {
fmt.Printf("%s: This will orphan instance %s, resetting it to INITIALIZED and permanently deleting its Terraform/OpenTofu state files. The next deployment will provision from scratch and may duplicate any resources tracked by the prior state. This is irreversible.\n", prettylogs.Orange("WARNING"), name)
} else {
fmt.Printf("%s: This will orphan instance %s, resetting it to INITIALIZED and clearing all of its Terraform/OpenTofu state locks.\n", prettylogs.Orange("WARNING"), name)
}
fmt.Printf("Type '%s' to confirm orphan: ", name)
reader := bufio.NewReader(os.Stdin)
answer, _ := reader.ReadString('\n')
if strings.TrimSpace(answer) != name {
fmt.Println("Orphan cancelled.")
return nil
}
}

mdClient, err := massdriver.NewClient()
if err != nil {
return fmt.Errorf("error initializing massdriver client: %w", err)
}

orphaned, err := mdClient.Instances.Orphan(ctx, name, instances.OrphanInput{DeleteState: deleteState})
if err != nil {
return err
}

fmt.Printf("✅ Instance `%s` orphaned (status: %s)\n", orphaned.ID, orphaned.Status)
fmt.Printf("🔗 %s\n", mdClient.URLs.Helper(ctx).InstanceURL(orphaned.ID))
return nil
}

func runInstanceList(cmd *cobra.Command, args []string) error {
ctx := context.Background()

Expand Down
2 changes: 2 additions & 0 deletions docs/generated/mass_deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Use these commands to inspect deployment history and logs:
- `mass deployment list <instance-id>` — list recent deployments for an instance
- `mass deployment get <deployment-id>` — show details for a single deployment
- `mass deployment logs <deployment-id>` — print log output from a deployment
- `mass deployment abort <deployment-id>` — abort a pending, approved, or running deployment


### Options
Expand All @@ -30,6 +31,7 @@ Use these commands to inspect deployment history and logs:
### SEE ALSO

* [mass](/cli/commands/mass) - Massdriver Cloud CLI
* [mass deployment abort](/cli/commands/mass_deployment_abort) - Abort a pending, approved, or running deployment
* [mass deployment get](/cli/commands/mass_deployment_get) - Get a deployment by ID
* [mass deployment list](/cli/commands/mass_deployment_list) - List deployments for an instance (most recent first)
* [mass deployment logs](/cli/commands/mass_deployment_logs) - Stream the log output from a deployment
62 changes: 62 additions & 0 deletions docs/generated/mass_deployment_abort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
id: mass_deployment_abort.md
slug: /cli/commands/mass_deployment_abort
title: Mass Deployment Abort
sidebar_label: Mass Deployment Abort
---
## mass deployment abort

Abort a pending, approved, or running deployment

### Synopsis

# Abort a Deployment

Cancels a `PENDING`, `APPROVED`, or `RUNNING` deployment. The deployment transitions to `ABORTED`.

A running deployment aborted mid-flight will not cancel or halt the
running provisioner. It only transitions the state of the Massdriver
deployment to `ABORTED`. Any partial infrastructure changes the
provisioner had applied will remain in place — the instance's state is
left as it was at the moment of abort.

To discard a `PROPOSED` deployment instead, use the `reject` flow.

## Usage

```shell
mass deployment abort <deployment-id> [--force]
```

## Flags

- `--force, -f`: Skip the confirmation prompt.

## Examples

```shell
mass deployment abort 12345678-1234-1234-1234-123456789012
mass deployment abort 12345678-1234-1234-1234-123456789012 --force
```


```
mass deployment abort <deployment-id> [flags]
```

### Examples

```
mass deployment abort 12345678-1234-1234-1234-123456789012 --force
```

### Options

```
-f, --force Skip confirmation prompt
-h, --help help for abort
```

### SEE ALSO

* [mass deployment](/cli/commands/mass_deployment) - Manage deployments
1 change: 1 addition & 0 deletions docs/generated/mass_instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ Instances are used to:
* [mass instance export](/cli/commands/mass_instance_export) - Export instances
* [mass instance get](/cli/commands/mass_instance_get) - Get an instance
* [mass instance list](/cli/commands/mass_instance_list) - List instances in an environment
* [mass instance orphan](/cli/commands/mass_instance_orphan) - Orphan an instance (reset to INITIALIZED, optionally clearing state locks)
* [mass instance version](/cli/commands/mass_instance_version) - Set instance version
60 changes: 60 additions & 0 deletions docs/generated/mass_instance_orphan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
id: mass_instance_orphan.md
slug: /cli/commands/mass_instance_orphan
title: Mass Instance Orphan
sidebar_label: Mass Instance Orphan
---
## mass instance orphan

Orphan an instance (reset to INITIALIZED, optionally clearing state locks)

### Synopsis

# Orphan an Instance

Resets an instance to `INITIALIZED`, clearing all of its Terraform/OpenTofu state locks. This is a break-glass operation for instances that are permanently stuck, such as instances in a `FAILED` state that cannot be successfully
provisioned or decommissioned. Active `RUNNING`, `PENDING`, and `APPROVED` deployments are bulk-aborted so a late worker will not retry deployments.

By default, the remote state files are preserved so the next deployment can re-attach to existing infrastructure. Pass `--delete-state` to also permanently delete the state files.

## Usage

```shell
mass instance orphan <project>-<env>-<manifest> [--delete-state] [--force]
```

## Flags

- `--force, -f`: Skip the confirmation prompt.
- `--delete-state`: Also permanently delete the instance's Terraform/OpenTofu state files. The next deployment will provision from scratch and may duplicate any resources tracked by the prior state. **Irreversible.**

## Examples

```shell
mass instance orphan api-prod-db
mass instance orphan api-prod-db --force
mass instance orphan api-prod-db --delete-state
```


```
mass instance orphan <project>-<env>-<manifest> [flags]
```

### Examples

```
mass instance orphan api-prod-db --force
```

### Options

```
--delete-state Also delete the remote Terraform/OpenTofu state files (irreversible)
-f, --force Skip confirmation prompt
-h, --help help for orphan
```

### SEE ALSO

* [mass instance](/cli/commands/mass_instance) - Manage instances of IaC deployed in environments.
1 change: 1 addition & 0 deletions docs/helpdocs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ Use these commands to inspect deployment history and logs:
- `mass deployment list <instance-id>` — list recent deployments for an instance
- `mass deployment get <deployment-id>` — show details for a single deployment
- `mass deployment logs <deployment-id>` — print log output from a deployment
- `mass deployment abort <deployment-id>` — abort a pending, approved, or running deployment
28 changes: 28 additions & 0 deletions docs/helpdocs/deployment/abort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Abort a Deployment

Cancels a `PENDING`, `APPROVED`, or `RUNNING` deployment. The deployment transitions to `ABORTED`.

A running deployment aborted mid-flight will not cancel or halt the
running provisioner. It only transitions the state of the Massdriver
deployment to `ABORTED`. Any partial infrastructure changes the
provisioner had applied will remain in place — the instance's state is
left as it was at the moment of abort.

To discard a `PROPOSED` deployment instead, use the `reject` flow.

## Usage

```shell
mass deployment abort <deployment-id> [--force]
```

## Flags

- `--force, -f`: Skip the confirmation prompt.

## Examples

```shell
mass deployment abort 12345678-1234-1234-1234-123456789012
mass deployment abort 12345678-1234-1234-1234-123456789012 --force
```
Loading
Loading