Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

daemon: allowing adding external http handlers #265

Merged
merged 10 commits into from
Aug 4, 2023
4 changes: 2 additions & 2 deletions internals/daemon/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/canonical/pebble/internals/overlord/state"
)

var api = []*Command{{
var API = []*Command{{
// See daemon.go:canAccess for details how the access is controlled.
Path: "/v1/system-info",
GuestOK: true,
Expand Down Expand Up @@ -107,7 +107,7 @@ var (
muxVars = mux.Vars
)

func v1SystemInfo(c *Command, r *http.Request, _ *userState) Response {
func v1SystemInfo(c *Command, r *http.Request, _ *UserState) Response {
state := c.d.overlord.State()
state.Lock()
defer state.Unlock()
Expand Down
8 changes: 4 additions & 4 deletions internals/daemon/api_changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func change2changeInfo(chg *state.Change) *changeInfo {
return chgInfo
}

func v1GetChanges(c *Command, r *http.Request, _ *userState) Response {
func v1GetChanges(c *Command, r *http.Request, _ *UserState) Response {
query := r.URL.Query()
qselect := query.Get("select")
if qselect == "" {
Expand Down Expand Up @@ -170,7 +170,7 @@ func v1GetChanges(c *Command, r *http.Request, _ *userState) Response {
return SyncResponse(chgInfos)
}

func v1GetChange(c *Command, r *http.Request, _ *userState) Response {
func v1GetChange(c *Command, r *http.Request, _ *UserState) Response {
changeID := muxVars(r)["id"]
st := c.d.overlord.State()
st.Lock()
Expand All @@ -183,7 +183,7 @@ func v1GetChange(c *Command, r *http.Request, _ *userState) Response {
return SyncResponse(change2changeInfo(chg))
}

func v1GetChangeWait(c *Command, r *http.Request, _ *userState) Response {
func v1GetChangeWait(c *Command, r *http.Request, _ *UserState) Response {
changeID := muxVars(r)["id"]
st := c.d.overlord.State()
st.Lock()
Expand Down Expand Up @@ -224,7 +224,7 @@ func v1GetChangeWait(c *Command, r *http.Request, _ *userState) Response {
return SyncResponse(change2changeInfo(change))
}

func v1PostChange(c *Command, r *http.Request, _ *userState) Response {
func v1PostChange(c *Command, r *http.Request, _ *UserState) Response {
chID := muxVars(r)["id"]
state := c.d.overlord.State()
state.Lock()
Expand Down
2 changes: 1 addition & 1 deletion internals/daemon/api_checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type checkInfo struct {
Threshold int `json:"threshold"`
}

func v1GetChecks(c *Command, r *http.Request, _ *userState) Response {
func v1GetChecks(c *Command, r *http.Request, _ *UserState) Response {
query := r.URL.Query()
level := plan.CheckLevel(query.Get("level"))
switch level {
Expand Down
2 changes: 1 addition & 1 deletion internals/daemon/api_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type execPayload struct {
Height int `json:"height"`
}

func v1PostExec(c *Command, req *http.Request, _ *userState) Response {
func v1PostExec(c *Command, req *http.Request, _ *UserState) Response {
var payload execPayload
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&payload); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions internals/daemon/api_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import (

const minBoundaryLength = 32

func v1GetFiles(_ *Command, req *http.Request, _ *userState) Response {
func v1GetFiles(_ *Command, req *http.Request, _ *UserState) Response {
query := req.URL.Query()
action := query.Get("action")
switch action {
Expand Down Expand Up @@ -337,7 +337,7 @@ func listFiles(path, pattern string, itself bool) ([]fileInfoResult, error) {
return result, nil
}

func v1PostFiles(_ *Command, req *http.Request, _ *userState) Response {
func v1PostFiles(_ *Command, req *http.Request, _ *UserState) Response {
contentType := req.Header.Get("Content-Type")
mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internals/daemon/api_health.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type healthInfo struct {
Healthy bool `json:"healthy"`
}

func v1Health(c *Command, r *http.Request, _ *userState) Response {
func v1Health(c *Command, r *http.Request, _ *UserState) Response {
query := r.URL.Query()
level := plan.CheckLevel(query.Get("level"))
switch level {
Expand Down
2 changes: 1 addition & 1 deletion internals/daemon/api_logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type serviceManager interface {
ServiceLogs(services []string, last int) (map[string]servicelog.Iterator, error)
}

func v1GetLogs(c *Command, _ *http.Request, _ *userState) Response {
func v1GetLogs(c *Command, _ *http.Request, _ *UserState) Response {
return logsResponse{
svcMgr: overlordServiceManager(c.d.overlord),
}
Expand Down
4 changes: 2 additions & 2 deletions internals/daemon/api_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/canonical/pebble/internals/plan"
)

func v1GetPlan(c *Command, r *http.Request, _ *userState) Response {
func v1GetPlan(c *Command, r *http.Request, _ *UserState) Response {
format := r.URL.Query().Get("format")
if format != "yaml" {
return statusBadRequest("invalid format %q", format)
Expand All @@ -42,7 +42,7 @@ func v1GetPlan(c *Command, r *http.Request, _ *userState) Response {
return SyncResponse(string(planYAML))
}

func v1PostLayers(c *Command, r *http.Request, _ *userState) Response {
func v1PostLayers(c *Command, r *http.Request, _ *UserState) Response {
var payload struct {
Action string `json:"action"`
Combine bool `json:"combine"`
Expand Down
8 changes: 4 additions & 4 deletions internals/daemon/api_services.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type serviceInfo struct {
CurrentSince *time.Time `json:"current-since,omitempty"` // pointer as omitempty doesn't work with time.Time directly
}

func v1GetServices(c *Command, r *http.Request, _ *userState) Response {
func v1GetServices(c *Command, r *http.Request, _ *UserState) Response {
names := strutil.MultiCommaSeparatedList(r.URL.Query()["names"])

servmgr := overlordServiceManager(c.d.overlord)
Expand All @@ -59,7 +59,7 @@ func v1GetServices(c *Command, r *http.Request, _ *userState) Response {
return SyncResponse(infos)
}

func v1PostServices(c *Command, r *http.Request, _ *userState) Response {
func v1PostServices(c *Command, r *http.Request, _ *UserState) Response {
var payload struct {
Action string `json:"action"`
Services []string `json:"services"`
Expand Down Expand Up @@ -212,11 +212,11 @@ func v1PostServices(c *Command, r *http.Request, _ *userState) Response {
return AsyncResponse(nil, change.ID())
}

func v1GetService(c *Command, r *http.Request, _ *userState) Response {
func v1GetService(c *Command, r *http.Request, _ *UserState) Response {
return statusBadRequest("not implemented")
}

func v1PostService(c *Command, r *http.Request, _ *userState) Response {
func v1PostService(c *Command, r *http.Request, _ *UserState) Response {
return statusBadRequest("not implemented")
}

Expand Down
2 changes: 1 addition & 1 deletion internals/daemon/api_signals.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type signalsPayload struct {
Services []string `json:"services"`
}

func v1PostSignals(c *Command, req *http.Request, _ *userState) Response {
func v1PostSignals(c *Command, req *http.Request, _ *UserState) Response {
var payload signalsPayload
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&payload); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internals/daemon/api_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"github.com/canonical/pebble/internals/overlord/state"
)

func v1GetTaskWebsocket(c *Command, req *http.Request, _ *userState) Response {
func v1GetTaskWebsocket(c *Command, req *http.Request, _ *UserState) Response {
vars := muxVars(req)
taskID := vars["task-id"]
websocketID := vars["websocket-id"]
Expand Down
2 changes: 1 addition & 1 deletion internals/daemon/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (s *apiSuite) daemon(c *check.C) *Daemon {
}

func apiCmd(path string) *Command {
for _, cmd := range api {
for _, cmd := range API {
if cmd.Path == path {
return cmd
}
Expand Down
4 changes: 2 additions & 2 deletions internals/daemon/api_warnings.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"github.com/canonical/pebble/internals/overlord/state"
)

func v1AckWarnings(c *Command, r *http.Request, _ *userState) Response {
func v1AckWarnings(c *Command, r *http.Request, _ *UserState) Response {
defer r.Body.Close()
var op struct {
Action string `json:"action"`
Expand All @@ -43,7 +43,7 @@ func v1AckWarnings(c *Command, r *http.Request, _ *userState) Response {
return SyncResponse(n)
}

func v1GetWarnings(c *Command, r *http.Request, _ *userState) Response {
func v1GetWarnings(c *Command, r *http.Request, _ *UserState) Response {
query := r.URL.Query()
var all bool
sel := query.Get("select")
Expand Down
15 changes: 9 additions & 6 deletions internals/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,14 @@ type Daemon struct {
mu sync.Mutex
}

// XXX Placeholder for now.
type userState struct{}
// UserState represents the state of an authenticated API user.
//
// The struct is currently empty as the behaviors haven't been implemented
// yet.
type UserState struct{}

// A ResponseFunc handles one of the individual verbs for a method
type ResponseFunc func(*Command, *http.Request, *userState) Response
type ResponseFunc func(*Command, *http.Request, *UserState) Response

// A Command routes a request to an individual per-verb ResponseFUnc
type Command struct {
Expand Down Expand Up @@ -149,7 +152,7 @@ const (
// - UserOK: any uid on the local system can access GET
// - AdminOnly: only the administrator can access this
// - UntrustedOK: can access this via the untrusted socket
func (c *Command) canAccess(r *http.Request, user *userState) accessResult {
func (c *Command) canAccess(r *http.Request, user *UserState) accessResult {
if c.AdminOnly && (c.UserOK || c.GuestOK || c.UntrustedOK) {
logger.Panicf("internal error: command cannot have AdminOnly together with any *OK flag")
}
Expand Down Expand Up @@ -210,7 +213,7 @@ func (c *Command) canAccess(r *http.Request, user *userState) accessResult {
return accessUnauthorized
}

func userFromRequest(state interface{}, r *http.Request) (*userState, error) {
func userFromRequest(state interface{}, r *http.Request) (*UserState, error) {
return nil, nil
}

Expand Down Expand Up @@ -401,7 +404,7 @@ func (d *Daemon) SetDegradedMode(err error) {
func (d *Daemon) addRoutes() {
d.router = mux.NewRouter()

for _, c := range api {
for _, c := range API {
c.d = d
if c.PathPrefix == "" {
d.router.Handle(c.Path, c).Name(c.Path)
Expand Down
43 changes: 34 additions & 9 deletions internals/daemon/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,31 @@ func (h *fakeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.lastMethod = r.Method
}

func (s *daemonSuite) TestAddCommand(c *C) {
const endpoint = "/v1/addedendpoint"
var handler fakeHandler
getCallback := func(c *Command, r *http.Request, s *UserState) Response {
handler.cmd = c
return &handler
}
command := Command{
Path: endpoint,
GuestOK: true,
GET: getCallback,
}

API = append(API, &command)
paul-rodriguez marked this conversation as resolved.
Show resolved Hide resolved
defer func() { API = API[:len(API)-1] }()
paul-rodriguez marked this conversation as resolved.
Show resolved Hide resolved

d := s.newDaemon(c)
d.Init()
d.Start()
defer d.Stop(nil)

result := d.router.Get(endpoint).GetHandler()
c.Assert(result, Equals, &command)
}

func (s *daemonSuite) TestExplicitPaths(c *C) {
s.socketPath = filepath.Join(c.MkDir(), "custom.socket")

Expand All @@ -121,7 +146,7 @@ func (s *daemonSuite) TestCommandMethodDispatch(c *C) {

cmd := &Command{d: s.newDaemon(c)}
handler := &fakeHandler{cmd: cmd}
rf := func(innerCmd *Command, req *http.Request, user *userState) Response {
rf := func(innerCmd *Command, req *http.Request, user *UserState) Response {
c.Assert(cmd, Equals, innerCmd)
return handler
}
Expand Down Expand Up @@ -160,7 +185,7 @@ func (s *daemonSuite) TestCommandRestartingState(c *C) {
d := s.newDaemon(c)

cmd := &Command{d: d}
cmd.GET = func(*Command, *http.Request, *userState) Response {
cmd.GET = func(*Command, *http.Request, *UserState) Response {
return SyncResponse(nil)
}
req, err := http.NewRequest("GET", "", nil)
Expand Down Expand Up @@ -210,7 +235,7 @@ func (s *daemonSuite) TestFillsWarnings(c *C) {
d := s.newDaemon(c)

cmd := &Command{d: d}
cmd.GET = func(*Command, *http.Request, *userState) Response {
cmd.GET = func(*Command, *http.Request, *UserState) Response {
return SyncResponse(nil)
}
req, err := http.NewRequest("GET", "", nil)
Expand Down Expand Up @@ -341,7 +366,7 @@ func (s *daemonSuite) TestUserAccess(c *C) {
func (s *daemonSuite) TestLoggedInUserAccess(c *C) {
d := s.newDaemon(c)

user := &userState{}
user := &UserState{}
get := &http.Request{Method: "GET", RemoteAddr: "pid=100;uid=42;socket=;"}
put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=42;socket=;"}

Expand Down Expand Up @@ -399,16 +424,16 @@ func (s *daemonSuite) TestSuperAccess(c *C) {
func (s *daemonSuite) TestAddRoutes(c *C) {
d := s.newDaemon(c)

expected := make([]string, len(api))
for i, v := range api {
expected := make([]string, len(API))
for i, v := range API {
if v.PathPrefix != "" {
expected[i] = v.PathPrefix
continue
}
expected[i] = v.Path
}

got := make([]string, 0, len(api))
got := make([]string, 0, len(API))
c.Assert(d.router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
got = append(got, route.GetName())
return nil
Expand Down Expand Up @@ -1028,10 +1053,10 @@ func doTestReq(c *C, cmd *Command, mth string) *httptest.ResponseRecorder {
func (s *daemonSuite) TestDegradedModeReply(c *C) {
d := s.newDaemon(c)
cmd := &Command{d: d}
cmd.GET = func(*Command, *http.Request, *userState) Response {
cmd.GET = func(*Command, *http.Request, *UserState) Response {
return SyncResponse(nil)
}
cmd.POST = func(*Command, *http.Request, *userState) Response {
cmd.POST = func(*Command, *http.Request, *UserState) Response {
return SyncResponse(nil)
}

Expand Down
Loading