Skip to content

Commit 4c4fbf8

Browse files
authored
Merge pull request #695 from jmpsec/api-tag-node-new
Tagging node using `osctrl-api`
2 parents cffc5be + 7e5a8ed commit 4c4fbf8

File tree

7 files changed

+147
-2
lines changed

7 files changed

+147
-2
lines changed

cmd/api/handlers/nodes.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,55 @@ func (h *HandlersApi) DeleteNodeHandler(w http.ResponseWriter, r *http.Request)
218218
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiGenericResponse{Message: "node deleted"})
219219
}
220220

221+
// TagNodeHandler - POST Handler to tag a node
222+
func (h *HandlersApi) TagNodeHandler(w http.ResponseWriter, r *http.Request) {
223+
// Debug HTTP if enabled
224+
if h.DebugHTTPConfig.Enabled {
225+
utils.DebugHTTPDump(h.DebugHTTP, r, h.DebugHTTPConfig.ShowBody)
226+
}
227+
// Extract environment
228+
envVar := r.PathValue("env")
229+
if envVar == "" {
230+
apiErrorResponse(w, "error with environment", http.StatusBadRequest, nil)
231+
return
232+
}
233+
// Get environment
234+
env, err := h.Envs.GetByUUID(envVar)
235+
if err != nil {
236+
apiErrorResponse(w, "error getting environment", http.StatusInternalServerError, nil)
237+
return
238+
}
239+
// Get context data and check access
240+
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
241+
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, env.UUID) {
242+
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
243+
return
244+
}
245+
var t types.ApiNodeTagRequest
246+
// Parse request JSON body
247+
if err := json.NewDecoder(r.Body).Decode(&t); err != nil {
248+
apiErrorResponse(w, "error parsing POST body", http.StatusInternalServerError, err)
249+
return
250+
}
251+
// Get node by UUID
252+
n, err := h.Nodes.GetByUUIDEnv(t.UUID, env.ID)
253+
if err != nil {
254+
if err.Error() == "record not found" {
255+
apiErrorResponse(w, "node not found", http.StatusNotFound, err)
256+
} else {
257+
apiErrorResponse(w, "error getting node", http.StatusInternalServerError, err)
258+
}
259+
return
260+
}
261+
if err := h.Tags.TagNode(t.Tag, n, ctx[ctxUser], false, t.Type); err != nil {
262+
apiErrorResponse(w, "error tagging node", http.StatusInternalServerError, err)
263+
return
264+
}
265+
// Serialize and serve JSON
266+
log.Debug().Msgf("Tagged node %s with %s", n.UUID, t.Tag)
267+
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiGenericResponse{Message: "node tagged"})
268+
}
269+
221270
// LookupNodeHandler - POST Handler to lookup a node by identifier
222271
func (h *HandlersApi) LookupNodeHandler(w http.ResponseWriter, r *http.Request) {
223272
// Debug HTTP if enabled

cmd/api/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ func osctrlAPIService() {
244244
muxAPI.Handle(
245245
"POST "+_apiPath(apiNodesPath)+"/{env}/delete",
246246
handlerAuthCheck(http.HandlerFunc(handlersApi.DeleteNodeHandler), flagParams.ConfigValues.Auth, flagParams.JWTConfigValues.JWTSecret))
247+
muxAPI.Handle(
248+
"POST "+_apiPath(apiNodesPath)+"/{env}/tag",
249+
handlerAuthCheck(http.HandlerFunc(handlersApi.TagNodeHandler), flagParams.ConfigValues.Auth, flagParams.JWTConfigValues.JWTSecret))
247250
muxAPI.Handle(
248251
"POST "+_apiPath(apiNodesPath)+"/lookup",
249252
handlerAuthCheck(http.HandlerFunc(handlersApi.LookupNodeHandler), flagParams.ConfigValues.Auth, flagParams.JWTConfigValues.JWTSecret))

cmd/cli/api-node.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,26 @@ func (api *OsctrlAPI) DeleteNode(env, identifier string) error {
6161
}
6262

6363
// TagNode to tag node in osctrl
64-
func (api *OsctrlAPI) TagNode(env, identifier, tag string) error {
64+
func (api *OsctrlAPI) TagNode(env, identifier, tag string, tagType uint) error {
65+
t := types.ApiNodeTagRequest{
66+
UUID: identifier,
67+
Tag: tag,
68+
Type: tagType,
69+
}
70+
var r types.ApiGenericResponse
71+
reqURL := path.Join(api.Configuration.URL, APIPath, APINodes, env, "tag")
72+
jsonMessage, err := json.Marshal(t)
73+
if err != nil {
74+
return fmt.Errorf("error marshaling data - %w", err)
75+
}
76+
jsonParam := bytes.NewReader(jsonMessage)
77+
rawN, err := api.PostGeneric(reqURL, jsonParam)
78+
if err != nil {
79+
return fmt.Errorf("error api request - %w - %s", err, string(rawN))
80+
}
81+
if err := json.Unmarshal(rawN, &r); err != nil {
82+
return fmt.Errorf("can not parse body - %w", err)
83+
}
6584
return nil
6685
}
6786

cmd/cli/node.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func tagNode(c *cli.Context) error {
178178
return fmt.Errorf("error tagging - %w", err)
179179
}
180180
} else if apiFlag {
181-
if err := osctrlAPI.TagNode(env, uuid, tag); err != nil {
181+
if err := osctrlAPI.TagNode(env, uuid, tag, tagTypeInt); err != nil {
182182
return fmt.Errorf("error tagging node - %w", err)
183183
}
184184
}

osctrl-api.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,59 @@ paths:
304304
security:
305305
- Authorization:
306306
- admin
307+
/nodes/{env}/tag:
308+
post:
309+
tags:
310+
- nodes
311+
summary: Tags node
312+
description: Tags an existing node by identifier (UUID, hostname or localname)
313+
operationId: TagNodeHandler
314+
parameters:
315+
- name: env
316+
in: path
317+
description: Name or UUID of the requested osctrl environment
318+
required: true
319+
schema:
320+
type: string
321+
requestBody:
322+
content:
323+
application/json:
324+
schema:
325+
$ref: "#/components/schemas/ApiNodeTagRequest"
326+
responses:
327+
200:
328+
description: successful operation
329+
content:
330+
application/json:
331+
schema:
332+
$ref: "#/components/schemas/ApiGenericResponse"
333+
400:
334+
description: bad request
335+
content:
336+
application/json:
337+
schema:
338+
$ref: "#/components/schemas/ApiErrorResponse"
339+
403:
340+
description: no access
341+
content:
342+
application/json:
343+
schema:
344+
$ref: "#/components/schemas/ApiErrorResponse"
345+
404:
346+
description: no nodes
347+
content:
348+
application/json:
349+
schema:
350+
$ref: "#/components/schemas/ApiErrorResponse"
351+
500:
352+
description: error tagging node
353+
content:
354+
application/json:
355+
schema:
356+
$ref: "#/components/schemas/ApiErrorResponse"
357+
security:
358+
- Authorization:
359+
- admin
307360
/nodes/lookup:
308361
post:
309362
tags:
@@ -2081,6 +2134,16 @@ components:
20812134
properties:
20822135
uuid:
20832136
type: string
2137+
ApiNodeTagRequest:
2138+
type: object
2139+
properties:
2140+
uuid:
2141+
type: string
2142+
tag:
2143+
type: string
2144+
type:
2145+
type: integer
2146+
format: int32
20842147
DistributedQuery:
20852148
type: object
20862149
properties:

pkg/tags/tags.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ const (
3434
ActionEdit string = "edit"
3535
// ActionRemove as action to remove a tag
3636
ActionRemove string = "remove"
37+
// ActionTag as action to tag a node
38+
ActionTag string = "tag"
39+
// ActionUntag as action to untag a node
40+
ActionUntag string = "untag"
3741
)
3842

3943
// AdminTag to hold all tags

pkg/types/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ type ApiNodeGenericRequest struct {
6666
UUID string `json:"uuid"`
6767
}
6868

69+
// ApiNodeTagRequest to receive tag node requests
70+
type ApiNodeTagRequest struct {
71+
UUID string `json:"uuid"`
72+
Tag string `json:"tag"`
73+
Type uint `json:"type"`
74+
}
75+
6976
// ApiLoginRequest to receive login requests
7077
type ApiLoginRequest struct {
7178
Username string `json:"username"`

0 commit comments

Comments
 (0)