Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce internal commands to manage execution state #112

Merged
merged 31 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
592667a
cmd/internal: setup internal cmds
joshi4 Jun 10, 2024
1c6e3ba
parse params
joshi4 Jun 11, 2024
13117f3
server/client: Create client for any socket path
joshi4 Jun 11, 2024
a022c05
server: DRY cleanup socket committed
joshi4 Jun 11, 2024
c68f189
client: Add a shutdown interface
joshi4 Jun 11, 2024
5aeb807
server/run: Add a run server
joshi4 Jun 11, 2024
3faaf58
savvy.zsh: only use zle to trigger runner
joshi4 Jun 12, 2024
0f937c8
server/client: SendShutdown returns an err
joshi4 Jun 12, 2024
d85b444
server/run: Update state based on commands
joshi4 Jun 12, 2024
44b6806
server/run: Add client for run server
joshi4 Jun 12, 2024
22fdc05
cmd/run: use runServer to coordinate state
joshi4 Jun 12, 2024
f5fbe4a
server/run: disambiguate client struct from pkg
joshi4 Jun 12, 2024
618d29b
cmd/internal: impl current and next
joshi4 Jun 12, 2024
192607b
change return data for next and current
joshi4 Jun 12, 2024
c95ee04
cmd/history: handle err starting record server
joshi4 Jun 12, 2024
59cba9d
run: start server
joshi4 Jun 12, 2024
f092657
server/run: read json messages instead of waiting for client to close…
joshi4 Jun 12, 2024
7336db6
close client conn when done
joshi4 Jun 12, 2024
a1ad5c6
savvy.zsh: use internal commands to run runbooks
joshi4 Jun 12, 2024
8f25216
cmd/next: return immediately if no prev command
joshi4 Jun 12, 2024
e798ff8
server/run: return val using json encoder
joshi4 Jun 12, 2024
9f2c561
server/run: allow idx == len(commands)
joshi4 Jun 13, 2024
fe92e6f
debug: savvy.zsh display next
joshi4 Jun 13, 2024
1e83830
add tests
joshi4 Jun 13, 2024
31a501d
setup/savvy.zsh: use 0 based arrays everywhere except when displaying…
joshi4 Jun 13, 2024
9560a81
internal/next: return 0 based array idx
joshi4 Jun 13, 2024
8d3725f
server/run: rm debug logs
joshi4 Jun 13, 2024
7edaf49
setup/savvy.zsh: cleanup debug logs
joshi4 Jun 13, 2024
24caf9b
server/run: Add note about why the run server is single threaded
joshi4 Jun 13, 2024
0f9f429
cmd/setup/bash-preexec.sh: impl run hooks with savvy internal cmds
joshi4 Jun 13, 2024
9183a17
cmd/write: dracula shows better on light terminals
joshi4 Jun 13, 2024
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
5 changes: 5 additions & 0 deletions cmd/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ func recordHistory(cmd *cobra.Command, _ []string) {
display.Info("Recording aborted")
return
}

if err != nil {
display.FatalErrWithSupportCTA(err)
return
}
defer ss.Close()

go func() {
Expand Down
35 changes: 35 additions & 0 deletions cmd/internal/current.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package internal

import (
"fmt"

"github.com/getsavvyinc/savvy-cli/display"
"github.com/getsavvyinc/savvy-cli/server/run"
"github.com/spf13/cobra"
)

// currentCmd represents the current command
var currentCmd = &cobra.Command{
Use: "current",
Short: "Get the command to run",
Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()
cl, err := run.NewDefaultClient(ctx)
if err != nil {
display.ErrorWithSupportCTA(err)
return
}

state, err := cl.CurrentState()
if err != nil {
display.ErrorWithSupportCTA(err)
return
}

fmt.Printf("%s", state.Command)
},
}

func init() {
InternalCmd.AddCommand(currentCmd)
}
14 changes: 14 additions & 0 deletions cmd/internal/internal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package internal

import (
"github.com/spf13/cobra"
)

// internalCmd represents the internal command
var InternalCmd = &cobra.Command{
Use: "internal",
Hidden: true,
Short: "Internal commands not meant to be used by end users.",
Run: func(cmd *cobra.Command, args []string) {
},
}
56 changes: 56 additions & 0 deletions cmd/internal/next.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package internal

import (
"fmt"
"os"

"github.com/getsavvyinc/savvy-cli/display"
"github.com/getsavvyinc/savvy-cli/server/run"
"github.com/spf13/cobra"
)

// nextCmd represents the next command
var nextCmd = &cobra.Command{
Use: "next",
Hidden: true,
Short: "Update runbook state to next step",
Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()
cl, err := run.NewDefaultClient(ctx)
if err != nil {
display.ErrorWithSupportCTA(err)
return
}

state, err := cl.CurrentState()
if err != nil {
display.ErrorWithSupportCTA(err)
os.Exit(1)
}

if state.Command != executedCommand {
fmt.Printf("%d", state.Index)
return
}

if err := cl.NextCommand(); err != nil {
display.ErrorWithSupportCTA(err)
os.Exit(1)
}

updated, err := cl.CurrentState()
if err != nil {
display.ErrorWithSupportCTA(err)
os.Exit(1)
}
fmt.Printf("%d", updated.Index)
},
}

var executedCommand string

func init() {
InternalCmd.AddCommand(nextCmd)

nextCmd.Flags().StringVarP(&executedCommand, "cmd", "c", "", "previously executed command")
}
81 changes: 81 additions & 0 deletions cmd/internal/set-param.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package internal

import (
"context"
"fmt"
"strings"

"github.com/charmbracelet/huh"
"github.com/spf13/cobra"
)

// subcommandCmd represents the subcommand command
var subcommandCmd = &cobra.Command{
Use: "set-param",
Short: "Prompt the user to set one ore parameters for their runbook",
Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()
fields := ParamFields(ctx, params)
var fs []huh.Field
for _, param := range params {
fs = append(fs, fields[param])
}

if len(fs) == 0 {
return
}

param := huh.NewGroup(fs...).Title(title)

if err := huh.NewForm(param).Run(); err != nil {
// TODO: handle error
panic(err)
}

for _, f := range fs {
i, ok := f.(*huh.Input)
if !ok {
continue
}
fmt.Println(i.GetKey(), i.GetValue())
}
},
}

var title string
var params []string

func init() {
InternalCmd.AddCommand(subcommandCmd)

// title flag
subcommandCmd.Flags().StringVarP(&title, "title", "t", "Set Params", "form title")
// params flag. This is a slice of strings
subcommandCmd.Flags().StringSliceVarP(&params, "params", "p", []string{}, "form params")
}

func ParamFields(ctx context.Context, params []string) map[string]huh.Field {
fields := map[string]huh.Field{}

for _, param := range params {
if _, ok := fields[param]; ok {
continue
}
title, desc := parseParam(param)
fields[param] = huh.NewInput().Title(title).Description(desc).Key(param)
}
return fields
}

const DefaultTitle = "Set Params"
const DefaultDescription = ""

func parseParam(param string) (string, string) {
if !strings.HasPrefix(param, "<") || !strings.HasSuffix(param, ">") {
return DefaultTitle, DefaultDescription
}

title := "Set " + param

return title, ""
}
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"log/slog"
"os"

"github.com/getsavvyinc/savvy-cli/cmd/internal"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -48,6 +49,7 @@ func init() {

// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.savvy.yaml)")
rootCmd.PersistentFlags().BoolVarP(&debugFlag, "debug", "d", false, "Enable debug mode")
rootCmd.AddCommand(internal.InternalCmd)

// Cobra also supports local flags, which will only run
// when this action is called directly.
Expand Down
23 changes: 22 additions & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"context"
"errors"
"fmt"
"io"
"log"
Expand All @@ -16,6 +17,7 @@ import (
"github.com/creack/pty"
"github.com/getsavvyinc/savvy-cli/client"
"github.com/getsavvyinc/savvy-cli/display"
"github.com/getsavvyinc/savvy-cli/server/run"
"github.com/getsavvyinc/savvy-cli/shell"
"github.com/muesli/cancelreader"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -88,10 +90,29 @@ func savvyRun(cmd *cobra.Command, args []string) {
}

func runRunbook(ctx context.Context, runbook *client.Runbook) error {
sh := shell.New("/tmp/savvy-socket")
ctx, cancelCtx := context.WithCancel(ctx)
defer cancelCtx()

rsrv, err := run.NewServerWithDefaultSocketPath(runbook)
if errors.Is(err, run.ErrAbortRun) {
display.Info("Run aborted")
return nil
}

if err != nil {
return err
}
defer rsrv.Close()

go func() {
rsrv.ListenAndServe()
// kill b/g shell if we exit early
cancelCtx()
os.Exit(1)
}()

sh := shell.New(rsrv.SocketPath())

c, err := sh.SpawnRunbookRunner(ctx, runbook)
if err != nil {
err := fmt.Errorf("run: failed to spawn shell %w", err)
Expand Down
9 changes: 4 additions & 5 deletions cmd/setup/bash-preexec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -443,8 +443,8 @@ SAVVY_NEXT_STEP=0
# Set up a function to run the next command in the runbook when the user presses C-n
savvy_runbook_runner() {
if [[ "${SAVVY_CONTEXT}" == "run" && "${SAVVY_NEXT_STEP}" -le "${#SAVVY_COMMANDS[@]}" ]] ; then
next_step_idx=${SAVVY_NEXT_STEP}
READLINE_LINE="${SAVVY_COMMANDS[next_step_idx]}"
next_step=$(savvy internal current)
READLINE_LINE="${next_step}"
READLINE_POINT=${#READLINE_LINE}
fi
}
Expand All @@ -454,9 +454,7 @@ savvy_run_pre_exec() {
# we want the command as it was typed in.
local cmd=$1
if [[ "${SAVVY_CONTEXT}" == "run" && "${SAVVY_NEXT_STEP}" -lt "${#SAVVY_COMMANDS[@]}" ]] ; then
if [[ "${cmd}" == "${SAVVY_COMMANDS[SAVVY_NEXT_STEP]}" ]] ; then
SAVVY_NEXT_STEP=$((SAVVY_NEXT_STEP+1))
fi
SAVVY_NEXT_STEP=$(savvy internal next --cmd="${cmd}")
fi
}

Expand All @@ -467,6 +465,7 @@ PROMPT_RED="\[$(tput setaf 1)\]"
PROMPT_RESET="\[$(tput sgr0)\]"

savvy_run_pre_cmd() {
# transorm 0 based index to 1 based index
local display_step=$((SAVVY_NEXT_STEP+1))
local size=${#SAVVY_COMMANDS[@]}

Expand Down
22 changes: 11 additions & 11 deletions cmd/setup/savvy.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ function __savvy_run_pre_exec__() {
# we want the command as it was typed in.
local cmd=$1
if [[ "${SAVVY_CONTEXT}" == "run" ]] ; then
if [[ "${cmd}" == "${SAVVY_COMMANDS[SAVVY_NEXT_STEP]}" ]] ; then
SAVVY_NEXT_STEP=$((SAVVY_NEXT_STEP+1))
fi
SAVVY_NEXT_STEP=$(savvy internal next --cmd="${cmd}")
fi
}

Expand All @@ -49,23 +47,25 @@ function __savvy_run_pre_cmd__() {
PS1="${orignal_ps1}"$'(%F{red}savvy run %f'" ${SAVVY_RUN_CURR})"" "
fi

if [[ "${SAVVY_CONTEXT}" == "run" && "${SAVVY_NEXT_STEP}" -gt "${#SAVVY_COMMANDS}" ]] ; then
if [[ "${SAVVY_CONTEXT}" == "run" && "${SAVVY_NEXT_STEP}" -ge "${#SAVVY_COMMANDS}" ]] ; then
# space at the end is important
PS1="${orignal_ps1}"$'%F{green} done%f \U1f60e '
fi

if [[ "${SAVVY_CONTEXT}" == "run" && "${SAVVY_NEXT_STEP}" -le "${#SAVVY_COMMANDS}" && "${#SAVVY_COMMANDS}" -gt 0 ]] ; then
RPS1="${original_rps1} %F{green}(${SAVVY_NEXT_STEP}/${#SAVVY_COMMANDS})"
if [[ "${SAVVY_CONTEXT}" == "run" && "${SAVVY_NEXT_STEP}" -lt "${#SAVVY_COMMANDS}" && "${#SAVVY_COMMANDS}" -gt 0 ]] ; then
# translate 0-based index to 1-based index
num=$((SAVVY_NEXT_STEP+1))
RPS1="${original_rps1} %F{green}(${num}/${#SAVVY_COMMANDS})"
else
RPS1="${original_rps1}"
fi
}

function __savvy_runbook_runner__() {

if [[ "${SAVVY_CONTEXT}" == "run" && "${SAVVY_NEXT_STEP}" -le "${#SAVVY_COMMANDS}" ]] ; then
next_step_idx=${SAVVY_NEXT_STEP}
BUFFER="${SAVVY_COMMANDS[next_step_idx]}"
if [[ "${SAVVY_CONTEXT}" == "run" ]] ; then
run_cmd=$(savvy internal current)
BUFFER="${run_cmd}"
zle end-of-line # Accept the line for editing
fi
}
Expand All @@ -80,10 +80,10 @@ original_rps1=$RPS1

SAVVY_COMMANDS=()
SAVVY_RUN_CURR=""
SAVVY_NEXT_STEP=1
SAVVY_NEXT_STEP=0
if [[ "${SAVVY_CONTEXT}" == "run" ]] ; then
zle -N zle-line-init __savvy_runbook_runner__
add-zle-hook-widget line-init __savvy_runbook_runner__
# add-zle-hook-widget line-init __savvy_runbook_runner__
# SAVVY_RUNBOOK_COMMANDS is a list of commands that savvy should run in the run context
SAVVY_COMMANDS=("${(@s:COMMA:)SAVVY_RUNBOOK_COMMANDS}")
SAVVY_RUN_CURR="${SAVVY_RUNBOOK_ALIAS}"
Expand Down
2 changes: 1 addition & 1 deletion cmd/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ var writeCmd = &cobra.Command{
if fileExists(filepath.Join(dirPath, filename)) {
var confirmation bool
confirmOverwrite := huh.NewConfirm().Title("File already exists").Description("Overwrite existing file?").Value(&confirmation)
if err := huh.NewForm(huh.NewGroup(confirmOverwrite)).Run(); err != nil {
if err := huh.NewForm(huh.NewGroup(confirmOverwrite)).WithTheme(huh.ThemeDracula()).Run(); err != nil {
display.FatalErrWithSupportCTA(err)
return
}
Expand Down
21 changes: 21 additions & 0 deletions server/cleanup/permission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cleanup

import (
"fmt"

"github.com/charmbracelet/huh"
"github.com/getsavvyinc/savvy-cli/server/mode"
)

func GetPermission(m mode.Mode) (bool, error) {
var confirmation bool
confirmCleanup := huh.NewConfirm().
Title(fmt.Sprintf("Multiple %s sessions detected", m)).
Affirmative("Continue here and kill other sessions").
Negative("Quit this session").
Value(&confirmation)
if err := huh.NewForm(huh.NewGroup(confirmCleanup)).Run(); err != nil {
return false, err
}
return confirmation, nil
}
Loading