router_utils.go

Copyright 2020, 2021, 2022 Red Hat, Inc

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

package
server
import
(
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
"strings"
httputils
"github.com/RedHatInsights/insights-operator-utils/http"
typeutils
"github.com/RedHatInsights/insights-operator-utils/types"
"github.com/rs/zerolog/log"
"github.com/RedHatInsights/insights-results-aggregator/types"
)
var
(
readRuleID
=
httputils
.
ReadRuleID
readErrorKey
=
httputils
.
ReadErrorKey
readRuleSelector
=
httputils
.
ReadRuleSelector
getRouterParam
=
httputils
.
GetRouterParam
getRouterPositiveIntParam
=
httputils
.
GetRouterPositiveIntParam
readClusterName
=
httputils
.
ReadClusterName
readOrganizationID
=
httputils
.
ReadOrganizationID
checkPermissions
=
httputils
.
CheckPermissions
readClusterNames
=
httputils
.
ReadClusterNames
readOrganizationIDs
=
httputils
.
ReadOrganizationIDs
readClusterListFromBody
=
httputils
.
ReadClusterListFromBody
)

readUserID retrieves user_id from request if it's not possible, it writes http error to the writer and returns false

func
readUserID
(
writer
http
.
ResponseWriter
,
request
*
http
.
Request
)
(
types
.
UserID
,
bool
)
{
userID
,
err
:=
getRouterParam
(
request
,
"user_id"
)
if
err
!=
nil
{
handleServerError
(
writer
,
err
)
return
""
,
false
}
userID
=
strings
.
TrimSpace
(
userID
)
if
userID
==
""
{
handleServerError
(
writer
,
&
RouterMissingParamError
{
ParamName
:
"user_id"
}
)
return
""
,
false
}
return
types
.
UserID
(
userID
)
,
true
}

readOrgID retrieves org_id from request if it's not possible, it writes http error to the writer and returns false

func
readOrgID
(
writer
http
.
ResponseWriter
,
request
*
http
.
Request
)
(
types
.
OrgID
,
bool
)
{
val
,
err
:=
getRouterPositiveIntParam
(
request
,
"org_id"
)
if
err
!=
nil
{
handleServerError
(
writer
,
err
)
return
0
,
false
}
orgID
,
err
:=
typeutils
.
Uint64ToUint32
(
val
)
if
err
!=
nil
{
handleServerError
(
writer
,
err
)
return
0
,
false
}
return
types
.
OrgID
(
orgID
)
,
true
}

readClusterListFromPath retrieves list of clusters from request's path if it's not possible, it writes http error to the writer and returns false

func
readClusterListFromPath
(
writer
http
.
ResponseWriter
,
request
*
http
.
Request
)
(
[
]
string
,
bool
)
{
rawClusterList
,
err
:=
getRouterParam
(
request
,
"cluster_list"
)
if
err
!=
nil
{
handleServerError
(
writer
,
err
)
return
[
]
string
{
}
,
false
}

basic check that should not happen in reality (because of Gorilla mux checks)

	
if
rawClusterList
==
""
{
handleServerError
(
writer
,
errors
.
New
(
"cluster list is empty"
)
)
return
[
]
string
{
}
,
false
}

split the list into items

	
clusterList
:=
strings
.
Split
(
rawClusterList
,
","
)

everything seems ok -> return list of clusters

	
return
clusterList
,
true
}
func
readRuleIDWithErrorKey
(
writer
http
.
ResponseWriter
,
request
*
http
.
Request
)
(
types
.
RuleID
,
types
.
ErrorKey
,
bool
)
{
ruleIDWithErrorKey
,
err
:=
getRouterParam
(
request
,
"rule_id"
)
if
err
!=
nil
{
const
message
=
"unable to get rule id"
log
.
Error
(
)
.
Err
(
err
)
.
Msg
(
message
)
handleServerError
(
writer
,
err
)
return
types
.
RuleID
(
"0"
)
,
types
.
ErrorKey
(
"0"
)
,
false
}
ruleID
,
errorKey
,
err
:=
getRuleAndErrorKeyFromRuleID
(
ruleIDWithErrorKey
)
if
err
!=
nil
{
log
.
Warn
(
)
.
Err
(
err
)
.
Msg
(
"Error splitting the rule identifier"
)
handleServerError
(
writer
,
&
RouterParsingError
{
ParamName
:
"rule_id"
,
ParamValue
:
ruleIDWithErrorKey
,
ErrString
:
err
.
Error
(
)
,
}
)
return
types
.
RuleID
(
"0"
)
,
types
.
ErrorKey
(
"0"
)
,
false
}
return
ruleID
,
errorKey
,
true
}
func
readRuleRatingFromBody
(
writer
http
.
ResponseWriter
,
request
*
http
.
Request
)
(
types
.
RuleRating
,
bool
)
{
var
rating
types
.
RuleRating

check if there's any body provided in the request sent by client

	
if
request
.
ContentLength
<=
0
{
err
:=
&
NoBodyError
{
}
handleServerError
(
writer
,
err
)
return
rating
,
false
}

try to decode the JSON from request

	
err
:=
json
.
NewDecoder
(
request
.
Body
)
.
Decode
(
&
rating
)
if
err
!=
nil
{
handleServerError
(
writer
,
err
)
return
rating
,
false
}

everything is ok

	
return
rating
,
true
}

readClusterRuleParams gets clustername, ruleid and error_key from current request

func
(
server
*
HTTPServer
)
readClusterRuleParams
(
writer
http
.
ResponseWriter
,
request
*
http
.
Request
,
)
(
clusterID
types
.
ClusterName
,
ruleID
types
.
RuleID
,
errorKey
types
.
ErrorKey
,
successful
bool
)
{
clusterID
,
successful
=
readClusterName
(
writer
,
request
)
if
!
successful
{
return
}
ruleID
,
successful
=
readRuleID
(
writer
,
request
)
if
!
successful
{
return
}
errorKey
,
successful
=
readErrorKey
(
writer
,
request
)
if
!
successful
{
return
}
clusterExists
,
err
:=
server
.
Storage
.
DoesClusterExist
(
clusterID
)
if
err
!=
nil
{
handleServerError
(
writer
,
err
)
successful
=
false
return
}
if
!
clusterExists
{
handleServerError
(
writer
,
&
types
.
ItemNotFoundError
{
ItemID
:
clusterID
}
)
successful
=
false
return
}
return
clusterID
,
ruleID
,
errorKey
,
true
}
func
getRuleAndErrorKeyFromRuleID
(
ruleIDWithErrorKey
string
)
(
ruleID
types
.
RuleID
,
errorKey
types
.
ErrorKey
,
err
error
,
)
{
ruleID
=
types
.
RuleID
(
"0"
)
errorKey
=
types
.
ErrorKey
(
"0"
)
err
=
nil
splitedRuleID
:=
strings
.
Split
(
ruleIDWithErrorKey
,
"|"
)
if
len
(
splitedRuleID
)
!=
2
{
err
=
&
types
.
ValidationError
{
ParamName
:
"rule"
,
ParamValue
:
ruleIDWithErrorKey
,
ErrString
:
"invalid rule ID, it must contain only rule ID and error key separated by |"
,
}
return
}
IDValidator
:=
regexp
.
MustCompile
(
`^[a-zA-Z_0-9.]+$`
)
isRuleIDValid
:=
IDValidator
.
MatchString
(
splitedRuleID
[
0
]
)
isErrorKeyValid
:=
IDValidator
.
MatchString
(
splitedRuleID
[
1
]
)
if
!
isRuleIDValid
||
!
isErrorKeyValid
{
err
=
fmt
.
Errorf
(
"invalid rule ID, each part of ID must contain only from latin characters, number, underscores or dots"
)
return
}
ruleID
=
types
.
RuleID
(
splitedRuleID
[
0
]
)
errorKey
=
types
.
ErrorKey
(
splitedRuleID
[
1
]
)
return
}

ReadClusterMapFromBody retrieves a list of clusters from the request body and generates a map of empty structs for fast access. If it's not possible, it writes an http error to the writer and returns false

func
ReadClusterMapFromBody
(
writer
http
.
ResponseWriter
,
request
*
http
.
Request
)
(
clusterMap
map
[
types
.
ClusterName
]
struct
{
}
,
successful
bool
,
)
{

check if there's any body provided in the request sent by client

	
if
request
.
ContentLength
<=
0
{
err
:=
&
NoBodyError
{
}
handleServerError
(
writer
,
err
)
return
}

decode cluster list from body

	
var
clustersInRequest
[
]
types
.
ClusterName
err
:=
json
.
NewDecoder
(
request
.
Body
)
.
Decode
(
&
clustersInRequest
)
if
err
!=
nil
{
handleServerError
(
writer
,
err
)
return
}
clusterMap
=
make
(
map
[
types
.
ClusterName
]
struct
{
}
,
len
(
clustersInRequest
)
)

more efficient to unmarshall a JSON list and iterate over it to create a map than marshaling a map

	
for
i
:=
range
clustersInRequest
{
clusterMap
[
clustersInRequest
[
i
]
]
=
struct
{
}
{
}
}
return
clusterMap
,
true
}