Skip to content

Commit

Permalink
chore: update readme, keybindings, colors. (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
leg100 committed Jul 12, 2024
1 parent b913600 commit 37c5cad
Show file tree
Hide file tree
Showing 74 changed files with 233 additions and 80 deletions.
219 changes: 179 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,15 @@ FLAGS
-p, --program STRING The default program to use with pug. (default: terraform)
-w, --workdir STRING The working directory containing modules. (default: .)
-t, --max-tasks INT The maximum number of parallel tasks. (default: 32)
--data-dir STRING Directory in which to store plan files. (default: $HOME/.pug)
--data-dir STRING Directory in which to store plan files. (default: /home/louis/.pug)
-e, --env STRING Environment variable to pass to terraform process. Can set more than once.
-a, --arg STRING CLI arg to pass to terraform process. Can set more than once.
-f, --first-page STRING The first page to open on startup. (default: modules)
-d, --debug Log bubbletea messages to messages.log
-v, --version Print version.
-l, --log-level STRING Logging level. (default: info)
-c, --config STRING Path to config file. (default: $HOME/.pug.yaml)
-c, --config STRING Path to config file. (default: /home/louis/.pug.yaml)
--disable-reload-after-apply Disable automatic reload of state following an apply.
-l, --log-level STRING Logging level (valid: info,debug,error,warn). (default: info)
```

Environment variables are specified by prefixing the value with `PUG_` and appending the equivalent flag value, replacing hyphens with underscores, e.g. `--max-tasks 100` is set via `PUG_MAX_TASKS=100`.
Expand All @@ -82,73 +83,211 @@ max-tasks: 100
Pug automatically loads variables from a .tfvars file. It looks for a file named `<workspace>.tfvars` in the module directory, where `<workspace>` is the name of the workspace. For example, if the workspace is named `dev` then it'll look for `dev.tfvars`. If the file exists then it'll pass the name to `terraform plan`, e.g. for a workspace named `dev`, it'll invoke `terraform plan -vars-file=dev.tfvars`.

## Resource hierarchy

There are several types of resources in pug:

* modules
* workspaces
* states
* tasks
## Pages

### Modules

![Modules screenshot](./demo/modules.png)

Press `m` to go to the modules page.

*Note: what Pug calls a module is equivalent to a [root module](https://developer.hashicorp.com/terraform/language/modules#the-root-module), i.e. a directory containing terraform configuration, including a state backend. It is not to be confused with a [child module](https://developer.hashicorp.com/terraform/language/modules#child-modules).*

A module is a directory of terraform configuration with a backend configuration. When Pug starts up, it looks recursively within the working directory, walking each directory and parsing any terraform configuration it finds. If the configuration contains a [state backend definition](https://developer.hashicorp.com/terraform/language/settings/backends/configuration) then Pug loads the directory as a module.
#### Key bindings

| Key | Description | Multi-select |
|--|--|--|
| `i`|Run `terraform init`|&check;|
| `f`|Run `terraform fmt`|&check;|
| `v`|Run `terraform validate`|&check;|
| `p`|Run `terraform plan`|&check;|
| `P`|Run `terraform plan -destroy`|&check;|
| `a`|Run `terraform apply`|&check;|
| `d`|Run `terraform apply -destroy`|&check;|
| `e`|Open module in editor|&cross;|
| `Ctrl+r`|Reload all modules|-|
| `Ctrl+w`|Reload module's workspaces|&check;|

Each module has zero or more workspaces. Following successful initialization the module has at least one workspace, named `default`. One workspace is set as the *current workspace* for the module. When you run a plan or apply on a module, it is created on its current workspace.
### Workspaces

If you add/remove modules outside of Pug, you can instruct Pug to reload modules by pressing `Ctrl-r` on the modules listing.
![Workspaces screenshot](./demo/workspaces.png)

### Workspaces
Press `w` to go to the workspaces page.

A workspace is directly equivalent to a [terraform workspace](https://developer.hashicorp.com/terraform/language/state/workspaces).
*Note: A workspace is directly equivalent to a [terraform workspace](https://developer.hashicorp.com/terraform/language/state/workspaces).*

When a module is loaded for the first time, Pug automatically creates a task to run `terraform workspace list`, to retrieve the list of workspaces for the module.
#### Key bindings

If you add/remove workspaces outside of Pug, you can instruct Pug to reload workspaces by pressing `Ctrl-w` on a module.
| Key | Description | Multi-select |
|--|--|--|
|`i`|Run `terraform init`|&check;|
|`f`|Run `terraform fmt`|&check;|
|`v`|Run `terraform validate`|&check;|
|`p`|Run `terraform plan`|&check;|
|`P`|Run `terraform plan -destroy`|&check;|
|`a`|Run `terraform apply`|&check;|
|`d`|Run `terraform apply -destroy`|&check;|
|`C`|Run `terraform workspace select`|&cross;|

### States
### State

Each workspace has state. Type `s` on a workspace to see its state, or type `s` on a module to see the state of its current workspace. You can also type `s` on a task, and it'll take you to the state of the task's workspace, or its module's current workspace.
![State screenshot](./demo/state.png)

When a workspace is loaded into Pug for the first time, a task is created to invoke `terraform state pull`, to retrieve the workspace's state. The task is also triggered after any task that alters the state, such as an apply or a state action.
Press `s` to go to the state page, listing a workspace's resources.

Various actions can be carried out on state:
#### Key bindings

* delete
* taint
* untaint
* targeted plan
* targeted destroy plan
* move (only a single resource at a time)
| Key | Description | Multi-select |
|--|--|--|
|`p`|Run `terraform plan -target`|&check;|
|`P`|Run `terraform plan -destroy -target`|&check;|
|`D`|Run `terraform state rm`|&check;|
|`M`|Run `terraform state mv`|&cross;|
|`Ctrl+t`|Run `terraform taint`|&check;|
|`Ctrl+u`|Run `terraform untaint`|&check;|
|`Ctrl+r`|Run `terraform state pull`|-|

### Tasks

Each invocation of terraform is represented as a task. A task belongs either to a workspace or a module.
![Tasks screenshot](./demo/tasks.png)

A task is either non-blocking or blocking. If it is blocking then it blocks tasks created after it that belong either to the same resource, or to a child resource. For example, an `init` task, which is a blocking task, runs on module "A". Another `init` task for module "A", created immediately afterwards, would be blocked until the former task has completed. Or a `plan` task created afterwards on workspace "default" on module "A", would also be blocked.
Press `t` to go to the tasks page.

A task starts in the `pending` state. It enters the `queued` state only if it is unblocked (see above). It remains in the `queued` state until there is available capacity, at which point it enters the `running` state. Capacity determines the maximum number of running tasks, and defaults to twice the number of cores on your system and can be overridden using `--max-tasks`.
#### Key bindings

An exception to this rule are tasks which are classified as *immediate*. Immediate tasks enter the running state regardless of available capacity. At time of writing only the `terraform workspace select` task is classified as such.
| Key | Description | Multi-select |
|--|--|--|
|`c`|Cancel task|&check;|
|`r`|Retry task|&check;|
|`Enter`|Full screen task output|&cross;|
|`S`|Toggle split screen|-|
|`+`|Increase split screen top pane|-|
|`-`|Decrease split screen top pane|-|
|`tab`|Switch split screen pane focus|-|
|`I`|Toggle task info sidebar|-|

A task can further be classed as *exclusive*. These tasks are globally mutually exclusive and cannot run concurrently. The only task classified as such is the `init` task, and only when you have enabled the [provider plugin cache](https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache) (the plugin cache does not permit concurrent writes).
### Task Group

A task can be canceled at any stage. If it is `running` then the current terraform process is sent a termination signal. Otherwise, in any other non-terminated state, the task is immediately set as `canceled`.
![Task group screenshot](./demo/task_group.png)

Creating multiple tasks, via a selection, creates a task group, and takes you to the task group page.

#### Key bindings

| Key | Description | Multi-select |
|--|--|--|
|`c`|Cancel task|&check;|
|`r`|Retry task|&check;|
|`Enter`|Full screen task output|&cross;|
|`S`|Toggle split screen|-|
|`+`|Increase split screen top pane|-|
|`-`|Decrease split screen top pane|-|
|`tab`|Switch split screen pane focus|-|
|`I`|Toggle task info sidebar|-|

### Task Groups Listing

![Task groups screenshot](./demo/task_groups.png)

Press `T` to go to the tasks groups page, which lists all task groups.

### Logs

![Logs screenshot](./demo/logs.png)

Press `l` to go to the logs page.

## Common Key bindings

### Global

These keys are valid on any page.

| Key | Description |
|--|--|
|`?`|Open help pane|
|`Ctrl+c`|Quit|
|`Esc`|Go to previous page|
|`m`|Go to modules page|
|`w`|Go to workspaces page|
|`s`|Go to state page\*|
|`t`|Go to tasks page|
|`T`|Go to task groups page|
|`l`|Go to logs|
|`Ctrl+s`|Toggle auto-scrolling of terraform output|

\* Only where the workspace can be ascertained.

### Selections

#### Plans
Items can be added or removed from a selection. Once selected, actions are carried out on the selected items if the action supports multiple selection.

Press `p` to create a plan. Under the hood, Pug invokes `terraform plan -out <plan-file>`. To apply the plan file, press `a` on the plan task.
| Key | Description |
|--|--|
|`<space>`|Toggle selection|
|`Ctrl+a`|Select all|
|`Ctrl+\`|Clear selection|
|`Ctrl+<space>`|Select range|

Press `d` to create a destroy plan. This is identical to a plan but with a `-destroy` flag.
### Filtering

#### Applies
![Filter mode screenshot](./demo/filter.png)

Items can be filtered to those containing a sub-string.

| Key | Description |
|--|--|
|`/`|Open and focus filter prompt|
|`Enter`|Unfocus filter prompt|
|`Esc`|Clear and close filter prompt|

### Navigation

Common vim key bindings are supported for navigation.

| Key | Description |
|--|--|
|`Up/k`|Up one row|
|`Down/j`|Down one row|
|`PgUp/b`|Up one page|
|`PgDown/f`|Down one page|
|`Home/g`|Go to top|
|`End/G`|Go to bottom|

## Reference

### Module

A module is a directory of terraform configuration with a backend configuration. When Pug starts up, it looks recursively within the working directory, walking each directory and parsing any terraform configuration it finds. If the configuration contains a [state backend definition](https://developer.hashicorp.com/terraform/language/settings/backends/configuration) then Pug loads the directory as a module.

Each module has zero or more workspaces. Following successful initialization the module has at least one workspace, named `default`. One workspace is set as the *current workspace* for the module. When you run a plan or apply on a module, it is created on its current workspace.

If you add/remove modules outside of Pug, you can instruct Pug to reload modules by pressing `Ctrl-r` on the modules listing.

### Workspace

Workspaces are parsed from the output of `terraform workspace list`, which is automatically run when:

* When a module is loaded into pug for the first time. Note the task may fail if the module is not correct initialized, and needs `terraform init` to be run.
* Following a `terraform init` task, but only if the module doesn't have a current workspace yet.

### Task

Each invocation of terraform is represented as a task.

A task is either non-blocking or blocking. Blocking tasks block their workspace or module, and prevent from further tasks from being enqueued until the blocking task has finished. For example, an `init` task, a blocking task, runs on module "A". Another `init` task for module "A", created immediately afterwards, would be blocked until the former task has completed. Or a `plan` task created afterwards on workspace "default" on module "A", would also be blocked. Blocking tasks in this manner prevent concurrent writes to resources that don't permit concurrent writes, such as the terraform state.

A task starts in the `pending` state. It enters the `queued` state only if it is unblocked (see above). It remains in the `queued` state until there is available capacity, at which point it enters the `running` state. Capacity determines the maximum number of running tasks, and defaults to twice the number of cores on your system and can be overridden using `--max-tasks`.

An exception to this rule are tasks which are classified as *immediate*. Immediate tasks enter the running state regardless of available capacity. At time of writing only the `terraform workspace select` task is classified as such.

A task can further be classed as *exclusive*. These tasks are globally mutually exclusive and cannot run concurrently. The only task classified as such is the `init` task, and only when you have enabled the [provider plugin cache](https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache) (the plugin cache does not permit concurrent writes).

A task can be canceled at any stage. If it is `running` then the current terraform process is sent a termination signal. Otherwise, in any other non-terminated state, the task is immediately set as `canceled`.

Press `a` to apply a module or workspace. Pug then requests your confirmation before invoking `terraform apply -auto-approve`.
### State

Alternatively, you can apply a plan file (see above).
When a workspace is loaded into Pug for the first time, a task is created to invoke `terraform state pull`, which retrieves workspace's state, and then the state is loaded into Pug. The task is also triggered after any task that alters the state, such as an apply or moving a resource in the state.

## Tofu support

Expand Down
Binary file modified demo/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/filter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/logs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/modules.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file added demo/state.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/task_group.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/task_groups.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/tasks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 27 additions & 3 deletions demo/vhs.tape
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ Set Padding 5

Hide
Type "demo/reset.sh" Enter
Type `TF_CLI_CONFIG_FILE=$PWD/mirror/mirror.tfrc go run main.go -w demo/configs` Enter
Type `TF_CLI_CONFIG_FILE=$PWD/mirror/mirror.tfrc go run main.go -w demo` Enter
Sleep 1s
Screenshot demo/modules_listing.png
Show

# show unintialized modules
Expand All @@ -31,7 +30,9 @@ Type "v"
# we're taken to the validate task group page
Sleep 0.5s
# enable task info side panel
Type "I"
Type "I" Sleep 0.5s
# take screen shot of tasks
Screenshot demo/tasks.png
# preview output for several tasks
Down Sleep 0.5s Down Sleep 0.5s Down Sleep 0.5s
# disable task info side panel
Expand All @@ -56,11 +57,17 @@ Ctrl+a Sleep 0.5s Type "a"
Sleep 0.5s Type "y"
# preview output for several tasks
Sleep 2s Down Sleep 1s Down Sleep 1s Down Sleep 1s
# take screen shot of apply task group
Screenshot demo/task_group.png

# go to workspaces
Type "w" Sleep 1s
# take screen shot of workspaces
Screenshot demo/workspaces.png
# filter dev workspaces
Type "/" Sleep 0.5s Type "dev" Sleep 0.5s Enter
# take screen shot of filter mode
Screenshot demo/filter.png Sleep 0.5s
# select all dev workspaces and auto-apply
Ctrl+a Sleep 0.5s Type "a"
# accept confirmation
Expand All @@ -70,6 +77,9 @@ Sleep 2s Down Sleep 1s Down Sleep 1s

# go back to modules
Type "m" Sleep 0.5s
# take screen shot of modules (sleep to ensure page doesn't switch too soon)
Screenshot demo/modules.png Sleep 0.5s

# go to state for current module
Type "s" Sleep 0.5s
# see preview for several resources
Expand All @@ -82,6 +92,8 @@ Sleep 1s

# go back to state
Escape Sleep 0.5s
# take screen shot of state
Screenshot demo/state.png

# delete another resource
Down Sleep 0.5s
Expand Down Expand Up @@ -130,3 +142,15 @@ Type "a"
Sleep 0.5s Type "y"
# look at apply task page for a bit
Sleep 1.5s

# go to task groups listing
Type "T"
# take screenshot of task groups
Screenshot demo/task_groups.png
Sleep 2s

# go to logs
Type "l"
# take screenshot of logs
Screenshot demo/logs.png
Sleep 2s
Binary file added demo/workspaces.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions hacks/reset_demo.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
find demo/configs -name .terraform -exec rm -rf {} \; > /dev/null 2>&1 || true
find demo/configs -name terraform.tfstate -exec rm {} \; > /dev/null 2>&1 || true
find demo/configs -name environment -exec rm {} \; > /dev/null 2>&1 || true
find demo/modules -name .terraform -exec rm -rf {} \; > /dev/null 2>&1 || true
find demo/modules -name terraform.tfstate -exec rm {} \; > /dev/null 2>&1 || true
find demo/modules -name terraform.tfstate.* -exec rm {} \; > /dev/null 2>&1 || true
find demo/modules -name environment -exec rm {} \; > /dev/null 2>&1 || true
4 changes: 4 additions & 0 deletions internal/app/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,10 @@ func TestWorkspace_Delete(t *testing.T) {
})
tm.Type("y")

waitFor(t, tm, func(s string) bool {
return matchPattern(t, `Task.*workspace delete.*dev.*modules/a.*exited`, s) &&
strings.Contains(s, `Deleted workspace "dev"!`)
})
}

func setupAndInitModuleWithTwoWorkspaces(t *testing.T) *testModel {
Expand Down
4 changes: 2 additions & 2 deletions internal/tui/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,9 @@ func (h *Helpers) GroupReport(group *task.Group, table bool) string {
if !table {
background = GroupReportBackgroundColor
}
slash := Regular.Background(background).Foreground(Black).Render("/")
slash := Regular.Background(background).Foreground(Grey).Render("/")
exited := Regular.Background(background).Foreground(Green).Render(fmt.Sprintf("%d", group.Exited()))
total := Regular.Background(background).Foreground(Black).Render(fmt.Sprintf("%d", len(group.Tasks)))
total := Regular.Background(background).Foreground(Blue).Render(fmt.Sprintf("%d", len(group.Tasks)))

s := fmt.Sprintf("%s%s%s", exited, slash, total)
if errored := group.Errored(); errored > 0 {
Expand Down
2 changes: 1 addition & 1 deletion internal/tui/keys/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,6 @@ var Global = global{
),
Help: key.NewBinding(
key.WithKeys("?"),
key.WithHelp("?", "help"),
key.WithHelp("?", "close help"),
),
}
Loading

0 comments on commit 37c5cad

Please sign in to comment.