-
Notifications
You must be signed in to change notification settings - Fork 561
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
snap: implement new snap reboot
command
#9021
Changes from 15 commits
4469021
b0e0833
1a5f7fe
2e4100f
bab0ca2
83ea27a
c98ad20
105ede5
4485991
5a34eff
5a99a04
c876309
4509e86
9da1973
688b602
421e70a
a37c40d
4a9c98e
e88e6d5
29d87b0
5b57db1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2020 Canonical Ltd | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 3 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/jessevdk/go-flags" | ||
|
||
"github.com/snapcore/snapd/i18n" | ||
) | ||
|
||
type cmdReboot struct { | ||
clientMixin | ||
Positional struct { | ||
Label string | ||
} `positional-args:"true"` | ||
|
||
RunMode bool `long:"run"` | ||
InstallMode bool `long:"install"` | ||
RecoverMode bool `long:"recover"` | ||
} | ||
|
||
var shortRebootHelp = i18n.G("Reboot into selected system and mode") | ||
var longRebootHelp = i18n.G(` | ||
The reboot command reboots the system into a particular mode of the selected | ||
recovery system. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should also explain what happens if mode or label are omitted |
||
`) | ||
|
||
func init() { | ||
addCommand("reboot", shortRebootHelp, longRebootHelp, func() flags.Commander { | ||
return &cmdReboot{} | ||
}, map[string]string{ | ||
// TRANSLATORS: This should not start with a lowercase letter. | ||
"run": i18n.G("Boot into run mode"), | ||
// TRANSLATORS: This should not start with a lowercase letter. | ||
"install": i18n.G("Boot into install mode"), | ||
// TRANSLATORS: This should not start with a lowercase letter. | ||
"recover": i18n.G("Boot into recover mode"), | ||
}, []argDesc{ | ||
{ | ||
// TRANSLATORS: This needs to begin with < and end with > | ||
name: i18n.G("<label>"), | ||
// TRANSLATORS: This should not start with a lowercase letter. | ||
desc: i18n.G("The recovery system label"), | ||
}, | ||
}) | ||
} | ||
|
||
func (x *cmdReboot) modeFromCommandline() (string, error) { | ||
var mode string | ||
|
||
for _, arg := range []struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice. |
||
enabled bool | ||
mode string | ||
}{ | ||
{x.RunMode, "run"}, | ||
{x.RecoverMode, "recover"}, | ||
{x.InstallMode, "install"}, | ||
} { | ||
if !arg.enabled { | ||
continue | ||
} | ||
if mode != "" { | ||
return "", fmt.Errorf("Please specify a single mode") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i18n? |
||
} | ||
mode = arg.mode | ||
} | ||
|
||
return mode, nil | ||
} | ||
|
||
func (x *cmdReboot) Execute(args []string) error { | ||
if len(args) > 0 { | ||
return ErrExtraArgs | ||
} | ||
|
||
mode, err := x.modeFromCommandline() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := x.client.DoSystemReboot(x.Positional.Label, mode); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this has a new name now, no? |
||
if x.Positional.Label != "" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. feels like we should move the error formatting logic into client itself, so here we just use what it returns, it means we get a less repetitive error |
||
return fmt.Errorf("cannot reboot into system %q: %v", x.Positional.Label, err) | ||
} | ||
return fmt.Errorf("cannot reboot: %v", err) | ||
} | ||
|
||
switch { | ||
case x.Positional.Label != "" && mode != "": | ||
fmt.Fprintf(Stdout, "Reboot into %q %q mode.\n", x.Positional.Label, mode) | ||
case x.Positional.Label != "": | ||
fmt.Fprintf(Stdout, "Reboot into %q.\n", x.Positional.Label) | ||
case mode != "": | ||
fmt.Fprintf(Stdout, "Reboot into %q mode.\n", mode) | ||
default: | ||
fmt.Fprintf(Stdout, "Reboot\n") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reboot is not immediate right? If so, should we say "Reboot ... requested" or something like this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A good question, happy about ideas :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not super important to be clear... My suggestion is to just append "requested" to all messages, e.g. "Reboot into %q mode requested". |
||
} | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2020 Canonical Ltd | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 3 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package main_test | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"strings" | ||
|
||
. "gopkg.in/check.v1" | ||
|
||
snap "github.com/snapcore/snapd/cmd/snap" | ||
) | ||
|
||
func (s *SnapSuite) TestRebootHelp(c *C) { | ||
msg := `Usage: | ||
snap.test reboot [reboot-OPTIONS] [<label>] | ||
|
||
The reboot command reboots the system into a particular mode of the selected | ||
recovery system. | ||
|
||
[reboot command options] | ||
--run Boot into run mode | ||
--install Boot into install mode | ||
--recover Boot into recover mode | ||
|
||
[reboot command arguments] | ||
<label>: The recovery system label | ||
` | ||
s.testSubCommandHelp(c, "reboot", msg) | ||
} | ||
|
||
func (s *SnapSuite) TestRebootHappy(c *C) { | ||
|
||
for _, tc := range []struct { | ||
cmdline []string | ||
expectedEndpoint string | ||
expectedJSON string | ||
expectedMsg string | ||
}{ | ||
{ | ||
cmdline: []string{"reboot"}, | ||
expectedEndpoint: "/v2/systems", | ||
expectedJSON: `{"action":"reboot","mode":""}`, | ||
expectedMsg: `Reboot`, | ||
}, | ||
{ | ||
cmdline: []string{"reboot", "--recover"}, | ||
expectedEndpoint: "/v2/systems", | ||
expectedJSON: `{"action":"reboot","mode":"recover"}`, | ||
expectedMsg: `Reboot into "recover" mode.`, | ||
}, | ||
{ | ||
cmdline: []string{"reboot", "20200101"}, | ||
expectedEndpoint: "/v2/systems/20200101", | ||
expectedJSON: `{"action":"reboot","mode":""}`, | ||
expectedMsg: `Reboot into "20200101".`, | ||
}, | ||
{ | ||
cmdline: []string{"reboot", "--recover", "20200101"}, | ||
expectedEndpoint: "/v2/systems/20200101", | ||
expectedJSON: `{"action":"reboot","mode":"recover"}`, | ||
expectedMsg: `Reboot into "20200101" "recover" mode.`, | ||
}, | ||
} { | ||
|
||
n := 0 | ||
s.ResetStdStreams() | ||
|
||
s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { | ||
switch n { | ||
case 0: | ||
c.Check(r.Method, Equals, "POST") | ||
c.Check(r.URL.Path, Equals, tc.expectedEndpoint, Commentf("%v", tc.cmdline)) | ||
c.Check(r.URL.RawQuery, Equals, "") | ||
body, err := ioutil.ReadAll(r.Body) | ||
c.Check(err, IsNil) | ||
c.Check(string(body), Equals, tc.expectedJSON+"\n") | ||
fmt.Fprintln(w, `{"type": "sync", "result": {}}`) | ||
default: | ||
c.Fatalf("expected to get 1 requests, now on %d", n+1) | ||
} | ||
|
||
n++ | ||
}) | ||
|
||
// The server side will work out if the request is valid | ||
rest, err := snap.Parser(snap.Client()).ParseArgs(tc.cmdline) | ||
c.Assert(err, IsNil) | ||
c.Assert(rest, DeepEquals, []string{}) | ||
c.Check(s.Stdout(), Equals, tc.expectedMsg+"\n", Commentf("%v", tc.cmdline)) | ||
c.Check(s.Stderr(), Equals, "") | ||
} | ||
} | ||
|
||
func (s *SnapSuite) TestRebootUnhappy(c *C) { | ||
s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { | ||
c.Fatalf("server should not be hit in this test") | ||
}) | ||
|
||
var tc = []struct { | ||
args []string | ||
errStr string | ||
}{ | ||
{ | ||
args: []string{"reboot", "--run", "--recover", "20200101"}, | ||
errStr: "Please specify a single mode", | ||
}, | ||
{ | ||
args: []string{"reboot", "--unknown-mode", "20200101"}, | ||
errStr: "unknown flag `unknown-mode'", | ||
}, | ||
} | ||
|
||
for _, t := range tc { | ||
_, err := snap.Parser(snap.Client()).ParseArgs(t.args) | ||
c.Check(err, ErrorMatches, t.errStr, Commentf(strings.Join(t.args, " "))) | ||
} | ||
} | ||
|
||
func (s *SnapSuite) TestRebootAPIFail(c *C) { | ||
n := 0 | ||
s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { | ||
switch n { | ||
case 0: | ||
c.Check(r.Method, Equals, "POST") | ||
c.Check(r.URL.Path, Equals, "/v2/systems/20200101") | ||
c.Check(r.URL.RawQuery, Equals, "") | ||
w.WriteHeader(404) | ||
fmt.Fprintln(w, `{"type": "error", "status-code":404, "result": {"message":"requested system does not exist"}}`) | ||
default: | ||
c.Fatalf("expected to get 1 requests, now on %d", n+1) | ||
} | ||
|
||
n++ | ||
}) | ||
_, err := snap.Parser(snap.Client()).ParseArgs([]string{"reboot", "--recover", "20200101"}) | ||
c.Assert(err, ErrorMatches, `cannot reboot into system "20200101": cannot request system action: requested system does not exist`) | ||
c.Check(s.Stdout(), Equals, "") | ||
c.Check(s.Stderr(), Equals, "") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably need to rethink "other" at some point and maybe add "Device Management", "Device" or "Ubuntu Core" or something what then has "model", "recovery", "reboot"