Skip to content

Commit 95e0caa

Browse files
committed
added endpoint to retrieve cmd executions
1 parent fca0929 commit 95e0caa

File tree

3 files changed

+154
-2
lines changed

3 files changed

+154
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### PVE API wrapper
1010
#### Added
1111
- `POST /custom-api/v1/lxc/{id}/exec-async` endpoint.
12+
- `GET /custom-api/v1/cmd/{id}` endpoint.
1213

1314
#### Fixed
1415
- custom handlers header content type.
16+
- missing custom handlers write response call on errors.
1517

1618
## [v0.6.1]
1719
### PVE API client

internal/models/cmd_execution.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package models
22

3-
import "github.com/google/uuid"
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/google/uuid"
8+
)
49

510
type CMDExecutionStatus string
611

@@ -15,6 +20,7 @@ type CMDExecution struct {
1520
Status CMDExecutionStatus `json:"status"`
1621
ExitCode *int `json:"exitCode"`
1722
Output *string `json:"output"`
23+
Error *string `json:"error"`
1824
}
1925

2026
type CMDExecutionModel struct {
@@ -86,3 +92,40 @@ func (s *CMDExecutionModel) SetSucceeded(
8692
) error {
8793
return s.upsert(id, CMD_EXEC_STATUS_SUCCEEDED, &exitCode, &output, nil)
8894
}
95+
96+
func (s *CMDExecutionModel) Get(id string) (result *CMDExecution, err error) {
97+
if id == "" {
98+
return result, errors.New(`please provide a valid id`)
99+
}
100+
101+
stmt := fmt.Sprintf(`SELECT
102+
id,
103+
status,
104+
exit_code,
105+
output,
106+
error
107+
FROM cmd_execution
108+
WHERE id = "%s"
109+
`, id)
110+
rows, err := s.models.db.Query(stmt)
111+
if err != nil {
112+
return result, err
113+
}
114+
defer rows.Close()
115+
116+
if !rows.Next() {
117+
return nil, nil
118+
}
119+
result = new(CMDExecution)
120+
if err := rows.Scan(
121+
&result.ID,
122+
&result.Status,
123+
&result.ExitCode,
124+
&result.Output,
125+
&result.Error,
126+
); err != nil {
127+
return nil, err
128+
}
129+
130+
return result, nil
131+
}

internal/server/server_custom_handlers.go

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func addCustomRoutes(m *http.ServeMux, s *server) {
1717
m.HandleFunc("GET /custom-api/v1/lxc/{id}/ip", getLXCIPHandlerV1(s))
1818
m.HandleFunc("POST /custom-api/v1/lxc/{id}/exec", postLXCCMDHandlerV1(s))
1919
m.HandleFunc("POST /custom-api/v1/lxc/{id}/exec-async", postLXCCMDAsyncHandlerV1(s))
20+
m.HandleFunc("GET /custom-api/v1/cmd/{id}", getCMDResultHandlerV1(s))
2021
}
2122

2223
// Get LXC IP godoc
@@ -71,6 +72,7 @@ func getLXCIPHandlerV1(s *server) http.HandlerFunc {
7172
)
7273
b, _ := httperr.Marshall()
7374
log.Println(r.Method, r.URL.Path, "failed", string(b))
75+
httperr.WriteResponse(w)
7476
return
7577
}
7678

@@ -83,6 +85,7 @@ func getLXCIPHandlerV1(s *server) http.HandlerFunc {
8385
)
8486
b, _ := httperr.Marshall()
8587
log.Println(r.Method, r.URL.Path, "failed", fmt.Sprintf("ip=%s", ip), fmt.Sprintf("err=%s", string(b)))
88+
httperr.WriteResponse(w)
8689
return
8790
}
8891

@@ -94,6 +97,7 @@ func getLXCIPHandlerV1(s *server) http.HandlerFunc {
9497
)
9598
b, _ := httperr.Marshall()
9699
log.Println(r.Method, r.URL.Path, "failed", string(b))
100+
httperr.WriteResponse(w)
97101
return
98102
}
99103

@@ -134,6 +138,7 @@ func postLXCCMDHandlerV1(s *server) http.HandlerFunc {
134138
)
135139
b, _ := httperr.Marshall()
136140
log.Println(r.Method, r.URL.Path, "failed", string(b))
141+
httperr.WriteResponse(w)
137142
return
138143
}
139144

@@ -145,6 +150,7 @@ func postLXCCMDHandlerV1(s *server) http.HandlerFunc {
145150
)
146151
b, _ := httperr.Marshall()
147152
log.Println(r.Method, r.URL.Path, "failed", string(b))
153+
httperr.WriteResponse(w)
148154
return
149155
}
150156

@@ -158,6 +164,7 @@ func postLXCCMDHandlerV1(s *server) http.HandlerFunc {
158164
)
159165
b, _ := httperr.Marshall()
160166
log.Println(r.Method, r.URL.Path, "failed", string(b))
167+
httperr.WriteResponse(w)
161168
return
162169
}
163170

@@ -170,6 +177,7 @@ func postLXCCMDHandlerV1(s *server) http.HandlerFunc {
170177
)
171178
b, _ := httperr.Marshall()
172179
log.Println(r.Method, r.URL.Path, "failed", string(b))
180+
httperr.WriteResponse(w)
173181
return
174182
}
175183
in := apidef.PostLXCExecRequest{}
@@ -181,6 +189,7 @@ func postLXCCMDHandlerV1(s *server) http.HandlerFunc {
181189
)
182190
b, _ := httperr.Marshall()
183191
log.Println(r.Method, r.URL.Path, "failed", string(b))
192+
httperr.WriteResponse(w)
184193
return
185194
}
186195

@@ -192,6 +201,7 @@ func postLXCCMDHandlerV1(s *server) http.HandlerFunc {
192201
)
193202
b, _ := httperr.Marshall()
194203
log.Println(r.Method, r.URL.Path, "failed", string(b))
204+
httperr.WriteResponse(w)
195205
return
196206
}
197207

@@ -204,6 +214,7 @@ func postLXCCMDHandlerV1(s *server) http.HandlerFunc {
204214
)
205215
b, _ := httperr.Marshall()
206216
log.Println(r.Method, r.URL.Path, "failed", string(b))
217+
httperr.WriteResponse(w)
207218
return
208219
}
209220

@@ -215,6 +226,7 @@ func postLXCCMDHandlerV1(s *server) http.HandlerFunc {
215226
)
216227
b, _ := httperr.Marshall()
217228
log.Println(r.Method, r.URL.Path, "failed", string(b))
229+
httperr.WriteResponse(w)
218230
return
219231
}
220232

@@ -242,7 +254,7 @@ func postLXCCMDHandlerV1(s *server) http.HandlerFunc {
242254
// @Failure 401 {object} errors.HTTPError
243255
// @Failure 404 {object} errors.HTTPError
244256
// @Failure 500 {object} errors.HTTPError
245-
// @Router /lxc/{id}/exec [post]
257+
// @Router /lxc/{id}/exec-async [post]
246258
func postLXCCMDAsyncHandlerV1(s *server) http.HandlerFunc {
247259
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
248260
log.Println(r.Method, r.URL.Path, "started")
@@ -255,6 +267,7 @@ func postLXCCMDAsyncHandlerV1(s *server) http.HandlerFunc {
255267
)
256268
b, _ := httperr.Marshall()
257269
log.Println(r.Method, r.URL.Path, "failed", string(b))
270+
httperr.WriteResponse(w)
258271
return
259272
}
260273

@@ -266,6 +279,7 @@ func postLXCCMDAsyncHandlerV1(s *server) http.HandlerFunc {
266279
)
267280
b, _ := httperr.Marshall()
268281
log.Println(r.Method, r.URL.Path, "failed", string(b))
282+
httperr.WriteResponse(w)
269283
return
270284
}
271285

@@ -279,6 +293,7 @@ func postLXCCMDAsyncHandlerV1(s *server) http.HandlerFunc {
279293
)
280294
b, _ := httperr.Marshall()
281295
log.Println(r.Method, r.URL.Path, "failed", string(b))
296+
httperr.WriteResponse(w)
282297
return
283298
}
284299

@@ -291,6 +306,7 @@ func postLXCCMDAsyncHandlerV1(s *server) http.HandlerFunc {
291306
)
292307
b, _ := httperr.Marshall()
293308
log.Println(r.Method, r.URL.Path, "failed", string(b))
309+
httperr.WriteResponse(w)
294310
return
295311
}
296312
in := apidef.PostLXCExecRequest{}
@@ -302,6 +318,7 @@ func postLXCCMDAsyncHandlerV1(s *server) http.HandlerFunc {
302318
)
303319
b, _ := httperr.Marshall()
304320
log.Println(r.Method, r.URL.Path, "failed", string(b))
321+
httperr.WriteResponse(w)
305322
return
306323
}
307324

@@ -313,6 +330,7 @@ func postLXCCMDAsyncHandlerV1(s *server) http.HandlerFunc {
313330
)
314331
b, _ := httperr.Marshall()
315332
log.Println(r.Method, r.URL.Path, "failed", string(b))
333+
httperr.WriteResponse(w)
316334
return
317335
}
318336

@@ -366,3 +384,92 @@ func postLXCCMDAsyncHandlerV1(s *server) http.HandlerFunc {
366384
}()
367385
})
368386
}
387+
388+
// Get CMD execution result godoc
389+
//
390+
// @Tags CMD
391+
// @Summary Get cmd execution result.
392+
// @Description Requires VM.Audit scope. Gets a cmd execution result using the id returned by an exec-async endpoint.
393+
// @Param id path string true "execution id"
394+
// @Accept json
395+
// @Produce json
396+
// @Success 200 {object} models.CMDExecution
397+
// @Failure 400 {object} errors.HTTPError
398+
// @Failure 401 {object} errors.HTTPError
399+
// @Failure 404 {object} errors.HTTPError
400+
// @Failure 500 {object} errors.HTTPError
401+
// @Router /cmd/{id} [get]
402+
func getCMDResultHandlerV1(s *server) http.HandlerFunc {
403+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
404+
log.Println(r.Method, r.URL.Path, "started")
405+
authorized, err := s.IsUserAuthorized(r, "VMS", "VM.Audit")
406+
if err != nil {
407+
httperr := errors.NewHTTPError(
408+
http.StatusInternalServerError,
409+
"unable to authorize request",
410+
err,
411+
)
412+
httperr.WriteResponse(w)
413+
b, _ := httperr.Marshall()
414+
log.Println(r.Method, r.URL.Path, "failed", string(b))
415+
return
416+
}
417+
418+
if !authorized {
419+
httperr := errors.NewHTTPError(
420+
http.StatusUnauthorized,
421+
"unauthorized",
422+
err,
423+
)
424+
httperr.WriteResponse(w)
425+
b, _ := httperr.Marshall()
426+
log.Println(r.Method, r.URL.Path, "failed", string(b))
427+
return
428+
}
429+
430+
id := r.PathValue("id")
431+
if id == "" {
432+
httperr := errors.NewHTTPError(
433+
http.StatusBadRequest,
434+
"id property is empty",
435+
err,
436+
)
437+
b, _ := httperr.Marshall()
438+
log.Println(r.Method, r.URL.Path, "failed", string(b))
439+
httperr.WriteResponse(w)
440+
return
441+
}
442+
443+
result, err := s.models.CMDExecution.Get(id)
444+
if err != nil {
445+
httperr := errors.NewHTTPError(
446+
http.StatusInternalServerError,
447+
"failed to retrieve cmd result",
448+
err,
449+
)
450+
b, _ := httperr.Marshall()
451+
log.Println(r.Method, r.URL.Path, "failed", fmt.Sprintf("id=%s", id), fmt.Sprintf("err=%s", string(b)))
452+
httperr.WriteResponse(w)
453+
return
454+
}
455+
456+
if result == nil {
457+
httperr := errors.NewHTTPError(
458+
http.StatusNotFound,
459+
"cmd execution result not found",
460+
err,
461+
)
462+
b, _ := httperr.Marshall()
463+
log.Println(r.Method, r.URL.Path, "failed", fmt.Sprintf("id=%s", id), fmt.Sprintf("err=%s", string(b)))
464+
httperr.WriteResponse(w)
465+
return
466+
}
467+
468+
b, _ := json.Marshal(result)
469+
w.Header().Add("content-type", "application/json")
470+
w.WriteHeader(http.StatusOK)
471+
w.Write(b)
472+
log.Println(r.Method, r.URL.Path, "succeeded")
473+
return
474+
})
475+
}

0 commit comments

Comments
 (0)