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