scrapli / scrapligo

scrapli, but in go!
MIT License
253 stars 36 forks source link

Support for changing the prompt temporariliy during a session #160

Closed netixx closed 10 months ago

netixx commented 11 months ago

Hello :),

I need to run shell commands on ios-xr, that means entering the "run" command, getting into the linux shell.

I tried with :

shellPattern := regexp.MustCompile(`(?im)^\[[^]]+\]\$\s*$`)
shellResp, err :=  d.SendCommands(
  append([]string{"run", "pwd"}),
  options.WithPromptPattern(shellPattern),
)

but it doesn't seem to be applied. I think PrompPattern is a channel option only (and not an operation option) ?

Is it possible to change the prompt during the same session ? In my case, changing it at the transport level wouldn't work because we still land on the normal NOS shell as we connect.

netixx commented 11 months ago

I was able to make it work rolling my own option:

func WithInterimPromptPattern(p *regexp.Regexp) util.Option {
    return func(o interface{}) error {
        c, ok := o.(*channel.OperationOptions)

        if !ok {
            return util.ErrIgnoredOption
        }

        c.InterimPromptPatterns = []*regexp.Regexp{p}

        return nil
    }
}

However I think that InterimPromptPatterns should be initialized inside the NewOperation, so that we can do append in the option.

Also, maybe there should be a simpler way to do that (like a switch-case in WithPromptPattern that works for operations.Channel also) ?

carlmontanari commented 11 months ago

ahh I think the "nicest" (subjective of course :P) way would be to update the platform (or make a custom platform) with the other shell/admin/rsp priv levels, I dug this up from python flavor from a few years back:

IOSXR_ADDTL_PRIVS = {
    "privilege_exec": (
        PrivilegeLevel(
            pattern=r"((?!sysadmin)^[a-z0-9.\-_@/:]{1,63}#\s?$)",
            name="privilege_exec",
            previous_priv="",
            deescalate="",
            escalate="",
            escalate_auth=False,
            escalate_prompt="",
        )
    ),
    "sysadmin": (
        PrivilegeLevel(
            pattern=r"(^sysadmin-vm:\d_rsp\d#\s?$)|(^sysadmin-vm:\d_rp\d#\s?$)",
            name="sysadmin",
            previous_priv="privilege_exec",
            deescalate="exit",
            escalate="admin",
            escalate_auth=False,
            escalate_prompt="",
        )
    ),
    "xrvm_rsp0": (
        PrivilegeLevel(
            pattern=r"^\[sysadmin-vm:0_RSP0:~\]\$\s?$",
            name="xrvm_rsp0",
            previous_priv="sysadmin",
            deescalate="exit",
            escalate="attach location 0/rsp0",
            escalate_auth=False,
            escalate_prompt="",
        )
    ),
    "xrvm_rp0": (
        PrivilegeLevel(
            pattern=r"^\[sysadmin-vm:0_RP0:~\]\$\s?$",
            name="xrvm_rp0",
            previous_priv="sysadmin",
            deescalate="exit",
            escalate="attach location 0/rp0",
            escalate_auth=False,
            escalate_prompt="",
        )
    ),
    "xrvm_rsp1": (
        PrivilegeLevel(
            pattern=r"^\[sysadmin-vm:0_RSP1:~\]\$\s?$",
            name="xrvm_rsp1",
            previous_priv="sysadmin",
            deescalate="exit",
            escalate="attach location 0/rsp1",
            escalate_auth=False,
            escalate_prompt="",
        )
    ),
}

With that you can do a SendConfig with a privilege level (yeah, send config is sorta confusing... but thats how it evolved historically since there originally was no different "command" type config levels but there could be different config levels i.e. exclusive/privileged/etc.) -- again, some python I have floating around could look like this:

# do xyz or w/e to figurę out what priv level ya wanna send the command at
command = "something to send"
priv_level = "xrvm_rp0"

dir_flash_result = await conn.send_config(
    config=command, privilege_level=priv_level
)

I believe all of this should work in scrapligo as is. Would this feel nicer for ya?

However I think that InterimPromptPatterns should be initialized inside the NewOperation, so that we can do append in the option.

If im following correctly -- maybe the option can just accept variadic? that would be no public api change (basically), and then could still append as many patterns as ya want?

netixx commented 10 months ago

Custom platform looks like the right solution indeed. Never used it in go yet :)

If im following correctly -- maybe the option can just accept variadic? that would be no public api change (basically), and then could still append as many patterns as ya want?

My issue here is that the c.InterimPromptPatterns array is not initialized anywhere, so in my WithInterimPromptPattern function I cannot do "append" to c.InterimPromptPatterns. Well I can but it will crash :)

carlmontanari commented 10 months ago

hah, well that makes a lot of sense :P ok will investigate this weekend and get something sorted out! thanks for all the help!

netixx commented 10 months ago

BTW I opened a PR to add the run shell to the list of privilege levels. I figured anyone could use it, I'll let you decide if it's indeed the case :)

carlmontanari commented 10 months ago

BTW I opened a PR to add the run shell to the list of privilege levels. I figured anyone could use it, I'll let you decide if it's indeed the case :)

saw it, and merged! thank you!

My issue here is that the c.InterimPromptPatterns array is not initialized anywhere, so in my WithInterimPromptPattern function I cannot do "append" to c.InterimPromptPatterns. Well I can but it will crash :)

I may be making myself even more confused, but since it will be nil from the get go (because not "initialized") you should still be able to append to that just fine? am I missing something else here?

type OperationOptions struct {
    InterimPromptPatterns []*regexp.Regexp
}

func main() {
    o := &OperationOptions{}

    o.InterimPromptPatterns = append(o.InterimPromptPatterns, regexp.MustCompile(`foo`))

    fmt.Println(o.InterimPromptPatterns)
}

^ works ok no? sorry I keep coming back to this long enough away that I lose all context and then get more confused :D hopefully above makes sense / is ok and we are good to go!

netixx commented 10 months ago

yes, my bad appending to a nil slice is legit in go :)

you are 100% right!!