github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/daemon/api_snapctl.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-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 "net/http" 24 25 "github.com/jessevdk/go-flags" 26 27 "github.com/snapcore/snapd/client" 28 "github.com/snapcore/snapd/i18n" 29 "github.com/snapcore/snapd/jsonutil" 30 "github.com/snapcore/snapd/overlord/auth" 31 "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" 32 ) 33 34 var ( 35 snapctlCmd = &Command{ 36 Path: "/v2/snapctl", 37 SnapOK: true, 38 POST: runSnapctl, 39 } 40 ) 41 42 var ctlcmdRun = ctlcmd.Run 43 44 func runSnapctl(c *Command, r *http.Request, user *auth.UserState) Response { 45 var snapctlPostData client.SnapCtlPostData 46 47 if err := jsonutil.DecodeWithNumber(r.Body, &snapctlPostData); err != nil { 48 return BadRequest("cannot decode snapctl request: %s", err) 49 } 50 51 if len(snapctlPostData.Args) == 0 { 52 return BadRequest("snapctl cannot run without args") 53 } 54 55 _, uid, _, err := ucrednetGet(r.RemoteAddr) 56 if err != nil { 57 return Forbidden("cannot get remote user: %s", err) 58 } 59 60 // Ignore missing context error to allow 'snapctl -h' without a context; 61 // Actual context is validated later by get/set. 62 context, _ := c.d.overlord.HookManager().Context(snapctlPostData.ContextID) 63 64 // make the data read from stdin available for the hook 65 // TODO: use a forwarded stdin here 66 if snapctlPostData.Stdin != nil { 67 context.Lock() 68 context.Set("stdin", snapctlPostData.Stdin) 69 context.Unlock() 70 } 71 72 stdout, stderr, err := ctlcmdRun(context, snapctlPostData.Args, uid) 73 if err != nil { 74 if e, ok := err.(*ctlcmd.UnsuccessfulError); ok { 75 result := map[string]interface{}{ 76 "stdout": string(stdout), 77 "stderr": string(stderr), 78 "exit-code": e.ExitCode, 79 } 80 return &resp{ 81 Type: ResponseTypeError, 82 Result: &errorResult{ 83 Message: e.Error(), 84 Kind: client.ErrorKindUnsuccessful, 85 Value: result, 86 }, 87 Status: 200, 88 } 89 } 90 if e, ok := err.(*ctlcmd.ForbiddenCommandError); ok { 91 return Forbidden(e.Error()) 92 } 93 if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp { 94 stdout = []byte(e.Error()) 95 } else { 96 return BadRequest("error running snapctl: %s", err) 97 } 98 } 99 100 if context != nil && context.IsEphemeral() { 101 context.Lock() 102 defer context.Unlock() 103 if err := context.Done(); err != nil { 104 return BadRequest(i18n.G("set failed: %v"), err) 105 } 106 } 107 108 result := map[string]string{ 109 "stdout": string(stdout), 110 "stderr": string(stderr), 111 } 112 113 return SyncResponse(result, nil) 114 }