github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 } 37 38 var systemsActionCmd = &Command{ 39 Path: "/v2/systems/{label}", 40 POST: postSystemsAction, 41 RootOnly: true, 42 } 43 44 type systemsResponse struct { 45 Systems []client.System `json:"systems,omitempty"` 46 } 47 48 func getSystems(c *Command, r *http.Request, user *auth.UserState) Response { 49 var rsp systemsResponse 50 51 seedSystems, err := c.d.overlord.DeviceManager().Systems() 52 if err != nil { 53 if err == devicestate.ErrNoSystems { 54 // no systems available 55 return SyncResponse(&rsp, nil) 56 } 57 58 return InternalError(err.Error()) 59 } 60 61 rsp.Systems = make([]client.System, 0, len(seedSystems)) 62 63 for _, ss := range seedSystems { 64 // untangle the model 65 66 actions := make([]client.SystemAction, 0, len(ss.Actions)) 67 for _, sa := range ss.Actions { 68 actions = append(actions, client.SystemAction{ 69 Title: sa.Title, 70 Mode: sa.Mode, 71 }) 72 } 73 74 rsp.Systems = append(rsp.Systems, client.System{ 75 Current: ss.Current, 76 Label: ss.Label, 77 Model: client.SystemModelData{ 78 Model: ss.Model.Model(), 79 BrandID: ss.Model.BrandID(), 80 DisplayName: ss.Model.DisplayName(), 81 }, 82 Brand: snap.StoreAccount{ 83 ID: ss.Brand.AccountID(), 84 Username: ss.Brand.Username(), 85 DisplayName: ss.Brand.DisplayName(), 86 Validation: ss.Brand.Validation(), 87 }, 88 Actions: actions, 89 }) 90 } 91 return SyncResponse(&rsp, nil) 92 } 93 94 type systemActionRequest struct { 95 Action string `json:"action"` 96 client.SystemAction 97 } 98 99 func postSystemsAction(c *Command, r *http.Request, user *auth.UserState) Response { 100 var req systemActionRequest 101 systemLabel := muxVars(r)["label"] 102 103 decoder := json.NewDecoder(r.Body) 104 if err := decoder.Decode(&req); err != nil { 105 return BadRequest("cannot decode request body into system action: %v", err) 106 } 107 if decoder.More() { 108 return BadRequest("extra content found in request body") 109 } 110 switch req.Action { 111 case "do": 112 return postSystemActionDo(c, systemLabel, &req) 113 case "reboot": 114 return postSystemActionReboot(c, systemLabel, &req) 115 default: 116 return BadRequest("unsupported action %q", req.Action) 117 } 118 } 119 120 // XXX: should deviceManager return more sensible errors here? 121 // E.g. UnsupportedActionError{systemLabel, mode} 122 // SystemDoesNotExistError{systemLabel} 123 func handleSystemActionErr(err error, systemLabel string) Response { 124 if os.IsNotExist(err) { 125 return NotFound("requested seed system %q does not exist", systemLabel) 126 } 127 if err == devicestate.ErrUnsupportedAction { 128 return BadRequest("requested action is not supported by system %q", systemLabel) 129 } 130 return InternalError(err.Error()) 131 } 132 133 // wrapped for unit tests 134 var deviceManagerReboot = func(dm *devicestate.DeviceManager, systemLabel, mode string) error { 135 return dm.Reboot(systemLabel, mode) 136 } 137 138 func postSystemActionReboot(c *Command, systemLabel string, req *systemActionRequest) Response { 139 dm := c.d.overlord.DeviceManager() 140 if err := deviceManagerReboot(dm, systemLabel, req.Mode); err != nil { 141 return handleSystemActionErr(err, systemLabel) 142 } 143 return SyncResponse(nil, nil) 144 } 145 146 func postSystemActionDo(c *Command, systemLabel string, req *systemActionRequest) Response { 147 if systemLabel == "" { 148 return BadRequest("system action requires the system label to be provided") 149 } 150 if req.Mode == "" { 151 return BadRequest("system action requires the mode to be provided") 152 } 153 154 sa := devicestate.SystemAction{ 155 Title: req.Title, 156 Mode: req.Mode, 157 } 158 if err := c.d.overlord.DeviceManager().RequestSystemAction(systemLabel, sa); err != nil { 159 return handleSystemActionErr(err, systemLabel) 160 } 161 return SyncResponse(nil, nil) 162 }