diff --git a/cmd/ps.go b/cmd/ps.go index 57361bdd9..bd57c0e04 100644 --- a/cmd/ps.go +++ b/cmd/ps.go @@ -122,16 +122,16 @@ func psMain(cmd *cobra.Command, args []string) exitCode { if err != nil { cmdutil.ErrPrintf("errored on %v: %v\n", k, err) } - var shell plugin.LoginShell - if entry.Attributes.HasLoginShell() { - shell = entry.Attributes.LoginShell() + var loginShell plugin.Shell + if entry.Attributes.HasOS() { + loginShell = entry.Attributes.OS().LoginShell } - if shell == plugin.UnknownShell { + if loginShell == plugin.UnknownShell { // Assume posix if unknown - shell = plugin.POSIXShell + loginShell = plugin.POSIXShell } - dispatcher := dispatchers[shell] + dispatcher := dispatchers[loginShell] ch, err := dispatcher.execPS(conn, k) if err != nil { cmdutil.ErrPrintf("errored on %v: %v\n", k, err) diff --git a/plugin/aws/ec2Instance.go b/plugin/aws/ec2Instance.go index 43b5d1a83..bc91a7614 100644 --- a/plugin/aws/ec2Instance.go +++ b/plugin/aws/ec2Instance.go @@ -153,7 +153,7 @@ func getAttributesAndMetadata(inst *ec2Client.Instance) (plugin.EntryAttributes, attr. SetCrtime(crtime). SetMtime(mtime). - SetLoginShell(shell) + SetOS(plugin.OS{LoginShell: shell}) meta := plugin.ToJSONObject(ec2InstanceMetadata{ Instance: inst, diff --git a/plugin/entryAttributes.go b/plugin/entryAttributes.go index 28de2cbca..b94aaa504 100644 --- a/plugin/entryAttributes.go +++ b/plugin/entryAttributes.go @@ -36,23 +36,36 @@ func ToJSONObject(v interface{}) JSONObject { return obj } -// LoginShell describes the type of shell execution occurs in. It can be used -// by the caller to decide what type of commands to run. -type LoginShell int +// Shell describes a command shell. +type Shell int -// Defines specific LoginShell classes you can configure +// Defines specific Shell classes you can configure const ( - UnknownShell LoginShell = iota + UnknownShell Shell = iota POSIXShell PowerShell ) var shellNames = [3]string{"unknown", "posixshell", "powershell"} -func (sh LoginShell) String() string { +func (sh Shell) String() string { return shellNames[sh] } +// OS contains information about the operating system of a compute-like entry +type OS struct { + // LoginShell describes the type of shell execution occurs in. It can be used + // by the caller to decide what type of commands to run. + LoginShell Shell +} + +// ToMap converts the OS struct data to a map. +func (o OS) ToMap() map[string]interface{} { + return map[string]interface{}{ + "login_shell": o.LoginShell.String(), + } +} + /* EntryAttributes represents an entry's attributes. We use a struct instead of a map for efficient memory allocation/deallocation, @@ -68,12 +81,12 @@ to do something like entry.SetAttributes(attr) */ type EntryAttributes struct { - atime time.Time - mtime time.Time - ctime time.Time - crtime time.Time - // TODO: make this OS, containing LoginShell and Platform - shell LoginShell + atime time.Time + mtime time.Time + ctime time.Time + crtime time.Time + os OS + hasOS bool // identifies that the OS struct has valid operating system information mode os.FileMode hasMode bool size uint64 @@ -162,19 +175,20 @@ func (a *EntryAttributes) SetCrtime(crtime time.Time) *EntryAttributes { return a } -// HasLoginShell returns true if the entry uses a particular login shell -func (a *EntryAttributes) HasLoginShell() bool { - return a.shell != UnknownShell +// HasOS returns true if the entry has information about its OS +func (a *EntryAttributes) HasOS() bool { + return a.hasOS } -// LoginShell returns the entry's login shell when using Exec -func (a *EntryAttributes) LoginShell() LoginShell { - return a.shell +// OS returns the entry's operating system information +func (a *EntryAttributes) OS() OS { + return a.os } -// SetLoginShell sets the entry's login shell when using Exec -func (a *EntryAttributes) SetLoginShell(shell LoginShell) *EntryAttributes { - a.shell = shell +// SetOS sets the entry's operating system information +func (a *EntryAttributes) SetOS(os OS) *EntryAttributes { + a.os = os + a.hasOS = true return a } @@ -228,8 +242,8 @@ func (a *EntryAttributes) ToMap() map[string]interface{} { if a.HasCrtime() { mp["crtime"] = a.Crtime() } - if a.HasLoginShell() { - mp["loginshell"] = shellNames[a.LoginShell()] + if a.HasOS() { + mp["os"] = a.OS().ToMap() } if a.HasMode() { // The mode string representation is the only portable representation. FileMode uses its own @@ -290,17 +304,29 @@ func (a *EntryAttributes) UnmarshalJSON(data []byte) error { } a.SetCrtime(t) } - if shell, ok := mp["loginshell"]; ok { - var sh LoginShell - for i, name := range shellNames { - if shell == name { - sh = LoginShell(i) - } + if obj, ok := mp["os"]; ok { + os, ok := obj.(map[string]interface{}) + if !ok { + return attrMungeError("os", fmt.Errorf("os must be an object")) } - if sh == UnknownShell { - return attrMungeError("shell", fmt.Errorf("provided unknown shell %v", shell)) + + var o OS + if obj, ok := os["login_shell"]; ok { + shell, ok := obj.(string) + if !ok { + return attrMungeError("os", fmt.Errorf("login_shell must be a string")) + } + for i, name := range shellNames { + if shell == name { + o.LoginShell = Shell(i) + } + } + if o.LoginShell == UnknownShell { + errTxt := "provided unknown login shell %v, must be %v or %v" + return attrMungeError("os", fmt.Errorf(errTxt, shell, PowerShell, POSIXShell)) + } } - a.SetLoginShell(sh) + a.SetOS(o) } if mode, ok := mp["mode"]; ok { md, err := munge.ToUintMode(mode) diff --git a/plugin/entryAttributes_test.go b/plugin/entryAttributes_test.go index 36edc2981..7aecabffa 100644 --- a/plugin/entryAttributes_test.go +++ b/plugin/entryAttributes_test.go @@ -105,14 +105,14 @@ func (suite *EntryAttributesTestSuite) TestEntryAttributes() { suite.Equal(expectedMp, attr.ToMap()) doUnmarshalJSONTests() - // Tests for login shell - suite.Equal(false, attr.HasLoginShell()) + // Tests for OS + suite.Equal(false, attr.HasOS()) suite.Equal(expectedMp, attr.ToMap()) - s := PowerShell - attr.SetLoginShell(s) - expectedMp["loginshell"] = "powershell" - suite.Equal(s, attr.LoginShell()) - suite.Equal(true, attr.HasLoginShell()) + o := OS{LoginShell: PowerShell} + attr.SetOS(o) + expectedMp["os"] = map[string]interface{}{"login_shell": "powershell"} + suite.Equal(o, attr.OS()) + suite.Equal(true, attr.HasOS()) suite.Equal(expectedMp, attr.ToMap()) doUnmarshalJSONTests() diff --git a/plugin/gcp/computeInst.go b/plugin/gcp/computeInst.go index 0e7815d1a..11680bb78 100644 --- a/plugin/gcp/computeInst.go +++ b/plugin/gcp/computeInst.go @@ -45,7 +45,7 @@ func newComputeInstance(inst *compute.Instance, c computeProjectService) *comput SetPartialMetadata(inst). Attributes(). SetCrtime(crtime). - SetLoginShell(plugin.POSIXShell) + SetOS(plugin.OS{LoginShell: plugin.POSIXShell}) return comp } diff --git a/volume/fs.go b/volume/fs.go index 43c078e38..fc7acf4e6 100644 --- a/volume/fs.go +++ b/volume/fs.go @@ -213,10 +213,10 @@ func (d *FS) selectShellCommand(posix []string, power []string) []string { } } -func (d *FS) loginShell() plugin.LoginShell { +func (d *FS) loginShell() plugin.Shell { attr := plugin.Attributes(d.executor) - if attr.HasLoginShell() { - return attr.LoginShell() + if shell := attr.OS().LoginShell; attr.HasOS() && shell != plugin.UnknownShell { + return shell } // Fallback to posix as a default return plugin.POSIXShell