github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/daemon/api_model.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 26 "github.com/snapcore/snapd/asserts" 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/overlord/state" 31 ) 32 33 var ( 34 serialModelCmd = &Command{ 35 Path: "/v2/model/serial", 36 GET: getSerial, 37 UserOK: true, 38 } 39 modelCmd = &Command{ 40 Path: "/v2/model", 41 POST: postModel, 42 GET: getModel, 43 UserOK: true, 44 } 45 ) 46 47 type assertType int 48 49 const ( 50 serialType assertType = iota 51 modelType 52 ) 53 54 var devicestateRemodel = devicestate.Remodel 55 56 type postModelData struct { 57 NewModel string `json:"new-model"` 58 } 59 60 type modelAssertJSONResponse struct { 61 Headers map[string]interface{} `json:"headers,omitempty"` 62 Body string `json:"body,omitempty"` 63 } 64 65 func postModel(c *Command, r *http.Request, _ *auth.UserState) Response { 66 defer r.Body.Close() 67 var data postModelData 68 decoder := json.NewDecoder(r.Body) 69 if err := decoder.Decode(&data); err != nil { 70 return BadRequest("cannot decode request body into remodel operation: %v", err) 71 } 72 rawNewModel, err := asserts.Decode([]byte(data.NewModel)) 73 if err != nil { 74 return BadRequest("cannot decode new model assertion: %v", err) 75 } 76 newModel, ok := rawNewModel.(*asserts.Model) 77 if !ok { 78 return BadRequest("new model is not a model assertion: %v", newModel.Type()) 79 } 80 81 st := c.d.overlord.State() 82 st.Lock() 83 defer st.Unlock() 84 85 chg, err := devicestateRemodel(st, newModel) 86 if err != nil { 87 return BadRequest("cannot remodel device: %v", err) 88 } 89 ensureStateSoon(st) 90 91 return AsyncResponse(nil, &Meta{Change: chg.ID()}) 92 93 } 94 95 // getModel gets the current model assertion using the DeviceManager 96 func getModel(c *Command, r *http.Request, _ *auth.UserState) Response { 97 opts, err := parseHeadersFormatOptionsFromURL(r.URL.Query()) 98 if err != nil { 99 return BadRequest(err.Error()) 100 } 101 102 st := c.d.overlord.State() 103 st.Lock() 104 defer st.Unlock() 105 106 devmgr := c.d.overlord.DeviceManager() 107 108 model, err := devmgr.Model() 109 if err == state.ErrNoState { 110 res := &errorResult{ 111 Message: "no model assertion yet", 112 Kind: client.ErrorKindAssertionNotFound, 113 Value: "model", 114 } 115 116 return &resp{ 117 Type: ResponseTypeError, 118 Result: res, 119 Status: 404, 120 } 121 } 122 if err != nil { 123 return InternalError("accessing model failed: %v", err) 124 } 125 126 if opts.jsonResult { 127 modelJSON := modelAssertJSONResponse{} 128 129 modelJSON.Headers = model.Headers() 130 if !opts.headersOnly { 131 modelJSON.Body = string(model.Body()) 132 } 133 134 return SyncResponse(modelJSON, nil) 135 } 136 137 return AssertResponse([]asserts.Assertion{model}, true) 138 } 139 140 // getSerial gets the current serial assertion using the DeviceManager 141 func getSerial(c *Command, r *http.Request, _ *auth.UserState) Response { 142 opts, err := parseHeadersFormatOptionsFromURL(r.URL.Query()) 143 if err != nil { 144 return BadRequest(err.Error()) 145 } 146 147 st := c.d.overlord.State() 148 st.Lock() 149 defer st.Unlock() 150 151 devmgr := c.d.overlord.DeviceManager() 152 153 serial, err := devmgr.Serial() 154 if err == state.ErrNoState { 155 res := &errorResult{ 156 Message: "no serial assertion yet", 157 Kind: client.ErrorKindAssertionNotFound, 158 Value: "serial", 159 } 160 161 return &resp{ 162 Type: ResponseTypeError, 163 Result: res, 164 Status: 404, 165 } 166 } 167 if err != nil { 168 return InternalError("accessing serial failed: %v", err) 169 } 170 171 if opts.jsonResult { 172 serialJSON := modelAssertJSONResponse{} 173 174 serialJSON.Headers = serial.Headers() 175 if !opts.headersOnly { 176 serialJSON.Body = string(serial.Body()) 177 } 178 179 return SyncResponse(serialJSON, nil) 180 } 181 182 return AssertResponse([]asserts.Assertion{serial}, true) 183 }