github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/daemon/api_systems.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package daemon 21 22 import ( 23 "encoding/json" 24 "net/http" 25 "os" 26 27 "github.com/snapcore/snapd/client" 28 "github.com/snapcore/snapd/overlord/auth" 29 "github.com/snapcore/snapd/overlord/devicestate" 30 "github.com/snapcore/snapd/snap" 31 ) 32 33 var systemsCmd = &Command{ 34 Path: "/v2/systems", 35 GET: getSystems, 36 ReadAccess: authenticatedAccess{}, 37 // this is awkward, we want the postSystemsAction function to be used 38 // when the label is empty too, but the router will not handle the request 39 // for /v2/systems with the systemsActionCmd and instead handles it through 40 // this command, so we need to set the POST for this command to essentially 41 // forward to that one 42 POST: postSystemsAction, 43 WriteAccess: rootAccess{}, 44 } 45 46 var systemsActionCmd = &Command{ 47 Path: "/v2/systems/{label}", 48 POST: postSystemsAction, 49 WriteAccess: rootAccess{}, 50 } 51 52 type systemsResponse struct { 53 Systems []client.System `json:"systems,omitempty"` 54 } 55 56 func getSystems(c *Command, r *http.Request, user *auth.UserState) Response { 57 var rsp systemsResponse 58 59 seedSystems, err := c.d.overlord.DeviceManager().Systems() 60 if err != nil { 61 if err == devicestate.ErrNoSystems { 62 // no systems available 63 return SyncResponse(&rsp) 64 } 65 66 return InternalError(err.Error()) 67 } 68 69 rsp.Systems = make([]client.System, 0, len(seedSystems)) 70 71 for _, ss := range seedSystems { 72 // untangle the model 73 74 actions := make([]client.SystemAction, 0, len(ss.Actions)) 75 for _, sa := range ss.Actions { 76 actions = append(actions, client.SystemAction{ 77 Title: sa.Title, 78 Mode: sa.Mode, 79 }) 80 } 81 82 rsp.Systems = append(rsp.Systems, client.System{ 83 Current: ss.Current, 84 Label: ss.Label, 85 Model: client.SystemModelData{ 86 Model: ss.Model.Model(), 87 BrandID: ss.Model.BrandID(), 88 DisplayName: ss.Model.DisplayName(), 89 }, 90 Brand: snap.StoreAccount{ 91 ID: ss.Brand.AccountID(), 92 Username: ss.Brand.Username(), 93 DisplayName: ss.Brand.DisplayName(), 94 Validation: ss.Brand.Validation(), 95 }, 96 Actions: actions, 97 }) 98 } 99 return SyncResponse(&rsp) 100 } 101 102 type systemActionRequest struct { 103 Action string `json:"action"` 104 client.SystemAction 105 } 106 107 func postSystemsAction(c *Command, r *http.Request, user *auth.UserState) Response { 108 var req systemActionRequest 109 systemLabel := muxVars(r)["label"] 110 111 decoder := json.NewDecoder(r.Body) 112 if err := decoder.Decode(&req); err != nil { 113 return BadRequest("cannot decode request body into system action: %v", err) 114 } 115 if decoder.More() { 116 return BadRequest("extra content found in request body") 117 } 118 switch req.Action { 119 case "do": 120 return postSystemActionDo(c, systemLabel, &req) 121 case "reboot": 122 return postSystemActionReboot(c, systemLabel, &req) 123 default: 124 return BadRequest("unsupported action %q", req.Action) 125 } 126 } 127 128 // XXX: should deviceManager return more sensible errors here? 129 // E.g. UnsupportedActionError{systemLabel, mode} 130 // SystemDoesNotExistError{systemLabel} 131 func handleSystemActionErr(err error, systemLabel string) Response { 132 if os.IsNotExist(err) { 133 return NotFound("requested seed system %q does not exist", systemLabel) 134 } 135 if err == devicestate.ErrUnsupportedAction { 136 return BadRequest("requested action is not supported by system %q", systemLabel) 137 } 138 return InternalError(err.Error()) 139 } 140 141 // wrapped for unit tests 142 var deviceManagerReboot = func(dm *devicestate.DeviceManager, systemLabel, mode string) error { 143 return dm.Reboot(systemLabel, mode) 144 } 145 146 func postSystemActionReboot(c *Command, systemLabel string, req *systemActionRequest) Response { 147 dm := c.d.overlord.DeviceManager() 148 if err := deviceManagerReboot(dm, systemLabel, req.Mode); err != nil { 149 return handleSystemActionErr(err, systemLabel) 150 } 151 return SyncResponse(nil) 152 } 153 154 func postSystemActionDo(c *Command, systemLabel string, req *systemActionRequest) Response { 155 if systemLabel == "" { 156 return BadRequest("system action requires the system label to be provided") 157 } 158 if req.Mode == "" { 159 return BadRequest("system action requires the mode to be provided") 160 } 161 162 sa := devicestate.SystemAction{ 163 Title: req.Title, 164 Mode: req.Mode, 165 } 166 if err := c.d.overlord.DeviceManager().RequestSystemAction(systemLabel, sa); err != nil { 167 return handleSystemActionErr(err, systemLabel) 168 } 169 return SyncResponse(nil) 170 }