content.go

Copyright 2020, 2021, 2022, 2023 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 content provides API to get rule's content by its rule id and error key. It takes all the work of caching rules taken from content service

package
content
import
(
"fmt"
"strings"
"sync"
"time"
"github.com/RedHatInsights/insights-operator-utils/generators"
utypes
"github.com/RedHatInsights/insights-operator-utils/types"
ctypes
"github.com/RedHatInsights/insights-results-types"
"github.com/rs/zerolog/log"
"github.com/RedHatInsights/insights-results-smart-proxy/services"
"github.com/RedHatInsights/insights-results-smart-proxy/types"
)
var
(
ruleContentDirectory
*
ctypes
.
RuleContentDirectory
ruleContentDirectoryReady
=
sync
.
NewCond
(
&
sync
.
Mutex
{
}
)
stopUpdateContentLoop
=
make
(
chan
struct
{
}
)
rulesWithContentStorage
=
getEmptyRulesWithContentMap
(
)
contentDirectoryTimeout
=
5
*
time
.
Second
dotReport
=
".report"
)
type
ruleIDAndErrorKey
struct
{
RuleID
ctypes
.
RuleID
ErrorKey
ctypes
.
ErrorKey
}

RulesWithContentStorage is a key:value structure to store processed rules. It's thread safe

type
RulesWithContentStorage
struct
{
rules
map
[
ctypes
.
RuleID
]
*
ctypes
.
RuleContent
rulesWithContent
map
[
ruleIDAndErrorKey
]
*
types
.
RuleWithContent

recommendationsWithContent map has the same contents as rulesWithContent but the keys are composite of "rule.module|ERROR_KEY" optimized for Insights Advisor

	
recommendationsWithContent
map
[
ctypes
.
RuleID
]
*
types
.
RuleWithContent
internalRuleIDs
[
]
ctypes
.
RuleID
externalRuleIDs
[
]
ctypes
.
RuleID
}

SetRuleContentDirectory is made for easy testing fake rules etc. from other directories

func
SetRuleContentDirectory
(
contentDir
*
ctypes
.
RuleContentDirectory
)
{
ruleContentDirectory
=
contentDir
}

GetRuleWithErrorKeyContent returns content for rule with error key

func
(
s
*
RulesWithContentStorage
)
GetRuleWithErrorKeyContent
(
ruleID
ctypes
.
RuleID
,
errorKey
ctypes
.
ErrorKey
,
)
(
*
types
.
RuleWithContent
,
bool
)
{
res
,
found
:=
s
.
rulesWithContent
[
ruleIDAndErrorKey
{
RuleID
:
ruleID
,
ErrorKey
:
errorKey
,
}
]
return
res
,
found
}

GetContentForRecommendation returns content for rule with error key

func
(
s
*
RulesWithContentStorage
)
GetContentForRecommendation
(
ruleID
ctypes
.
RuleID
,
)
(
*
types
.
RuleWithContent
,
bool
)
{
res
,
found
:=
s
.
recommendationsWithContent
[
ruleID
]
return
res
,
found
}

GetAllContentV1 returns content for rule for api v1

func
(
s
*
RulesWithContentStorage
)
GetAllContentV1
(
)
[
]
types
.
RuleContentV1
{
res
:=
make
(
[
]
types
.
RuleContentV1
,
0
,
len
(
s
.
rules
)
)
for
_
,
rule
:=
range
s
.
rules
{
res
=
append
(
res
,
RuleContentToV1
(
rule
)
)
}
return
res
}

GetAllContentV2 returns content for api/v2

func
(
s
*
RulesWithContentStorage
)
GetAllContentV2
(
)
[
]
types
.
RuleContentV2
{
res
:=
make
(
[
]
types
.
RuleContentV2
,
0
,
len
(
s
.
rules
)
)
for
_
,
rule
:=
range
s
.
rules
{
res
=
append
(
res
,
RuleContentToV2
(
rule
)
)
}
return
res
}

SetRuleWithContent sets content for rule with error key

func
(
s
*
RulesWithContentStorage
)
SetRuleWithContent
(
ruleID
ctypes
.
RuleID
,
errorKey
ctypes
.
ErrorKey
,
ruleWithContent
*
types
.
RuleWithContent
,
)
{
compositeRuleID
,
err
:=
generators
.
GenerateCompositeRuleID
(
ctypes
.
RuleFQDN
(
ruleID
)
,
errorKey
)
if
err
==
nil
{
s
.
recommendationsWithContent
[
compositeRuleID
]
=
ruleWithContent
}
else
{
log
.
Warn
(
)
.
Err
(
err
)
.
Msgf
(
"Error generating composite rule ID for [%v] and [%v]"
,
ruleID
,
errorKey
)
}
s
.
rulesWithContent
[
ruleIDAndErrorKey
{
RuleID
:
ruleID
,
ErrorKey
:
errorKey
,
}
]
=
ruleWithContent
if
ruleWithContent
.
Internal
{
s
.
internalRuleIDs
=
append
(
s
.
internalRuleIDs
,
compositeRuleID
)
}
else
{
s
.
externalRuleIDs
=
append
(
s
.
externalRuleIDs
,
compositeRuleID
)
}
}

SetRule sets content for rule

func
(
s
*
RulesWithContentStorage
)
SetRule
(
ruleID
ctypes
.
RuleID
,
ruleContent
*
ctypes
.
RuleContent
,
)
{
s
.
rules
[
ruleID
]
=
ruleContent
}

GetRuleIDs gets rule IDs for rules (rule modules)

func
(
s
*
RulesWithContentStorage
)
GetRuleIDs
(
)
[
]
string
{
ruleIDs
:=
make
(
[
]
string
,
0
,
len
(
s
.
rules
)
)
for
_
,
ruleContent
:=
range
s
.
rules
{
ruleIDs
=
append
(
ruleIDs
,
ruleContent
.
Plugin
.
PythonModule
)
}
return
ruleIDs
}

GetInternalRuleIDs returns the composite rule IDs ("| format") of internal rules

func
(
s
*
RulesWithContentStorage
)
GetInternalRuleIDs
(
)
[
]
ctypes
.
RuleID
{
return
s
.
internalRuleIDs
}

GetExternalRuleIDs returns the composite rule IDs ("| format") of external rules

func
(
s
*
RulesWithContentStorage
)
GetExternalRuleIDs
(
)
[
]
ctypes
.
RuleID
{
return
s
.
externalRuleIDs
}

GetExternalRuleSeverities returns a map of external rule IDs and their severity (total risk) along with a list of unique severities

func
(
s
*
RulesWithContentStorage
)
GetExternalRuleSeverities
(
)
(
severityMap
map
[
ctypes
.
RuleID
]
int
,
uniqueSeverities
[
]
int
,
)
{
severityMap
=
make
(
map
[
ctypes
.
RuleID
]
int
)
uniqueMap
:=
make
(
map
[
int
]
interface
{
}
)
for
_
,
ruleID
:=
range
s
.
externalRuleIDs
{
totalRisk
:=
s
.
recommendationsWithContent
[
ruleID
]
.
TotalRisk
severityMap
[
ruleID
]
=
totalRisk
uniqueMap
[
totalRisk
]
=
nil
}
for
k
:=
range
uniqueMap
{
uniqueSeverities
=
append
(
uniqueSeverities
,
k
)
}
return
}

GetExternalRulesManagedInfo returns a map of rule IDs and the information whether a rule is managed (has osd_customer tag) or not

func
(
s
*
RulesWithContentStorage
)
GetExternalRulesManagedInfo
(
)
(
managedMap
map
[
ctypes
.
RuleID
]
bool
)
{
managedMap
=
make
(
map
[
ctypes
.
RuleID
]
bool
)
for
_
,
ruleID
:=
range
s
.
externalRuleIDs
{
managedMap
[
ruleID
]
=
s
.
recommendationsWithContent
[
ruleID
]
.
OSDCustomer
}
return
}

RuleContentDirectoryTimeoutError is used, when the content directory is empty for too long time

type
RuleContentDirectoryTimeoutError
struct
{
}
func
(
e
*
RuleContentDirectoryTimeoutError
)
Error
(
)
string
{
return
"Content directory cache has been empty for too long time; timeout triggered"
}

WaitForContentDirectoryToBeReady ensures the rule content directory is safe to read/write

func
WaitForContentDirectoryToBeReady
(
)
error
{

according to the example in the official dock, lock is required here

	
if
ruleContentDirectory
==
nil
{
ruleContentDirectoryReady
.
L
.
Lock
(
)
done
:=
make
(
chan
struct
{
}
)
go
func
(
)
{
ruleContentDirectoryReady
.
Wait
(
)
close
(
done
)
}
(
)
select
{
case
<-
done
:
case
<-
time
.
After
(
contentDirectoryTimeout
)
:
err
:=
&
RuleContentDirectoryTimeoutError
{
}
log
.
Error
(
)
.
Err
(
err
)
.
Msg
(
"Cannot retrieve content"
)
return
err
}
ruleContentDirectoryReady
.
L
.
Unlock
(
)
}
return
nil
}

GetRuleWithErrorKeyContent returns content for rule with provided rule id and error key. Caching is done under the hood, don't worry about it.

func
GetRuleWithErrorKeyContent
(
ruleID
ctypes
.
RuleID
,
errorKey
ctypes
.
ErrorKey
,
)
(
*
types
.
RuleWithContent
,
error
)
{

to be sure the data is there

	
err
:=
WaitForContentDirectoryToBeReady
(
)
if
err
!=
nil
{
return
nil
,
err
}
ruleID
=
ctypes
.
RuleID
(
strings
.
TrimSuffix
(
string
(
ruleID
)
,
dotReport
)
)
res
,
found
:=
rulesWithContentStorage
.
GetRuleWithErrorKeyContent
(
ruleID
,
errorKey
)
if
!
found
{
return
nil
,
&
utypes
.
ItemNotFoundError
{
ItemID
:
fmt
.
Sprintf
(
"%v/%v"
,
ruleID
,
errorKey
)
}
}
return
res
,
nil
}

GetContentForRecommendation returns content for rule with provided composite rule ID

func
GetContentForRecommendation
(
ruleID
ctypes
.
RuleID
,
)
(
*
types
.
RuleWithContent
,
error
)
{
err
:=
WaitForContentDirectoryToBeReady
(
)
if
err
!=
nil
{
return
nil
,
err
}
res
,
found
:=
rulesWithContentStorage
.
GetContentForRecommendation
(
ruleID
)
if
!
found
{
return
nil
,
&
utypes
.
ItemNotFoundError
{
ItemID
:
fmt
.
Sprintf
(
"%v"
,
ruleID
)
}
}
return
res
,
nil
}

GetRuleContentV1 returns content for rule with provided rule id Caching is done under the hood, don't worry about it.

func
GetRuleContentV1
(
ruleID
ctypes
.
RuleID
)
(
*
types
.
RuleContentV1
,
error
)
{
res
,
err
:=
getRuleContent
(
ruleID
)
if
err
==
nil
{
resV1
:=
RuleContentToV1
(
res
)
return
&
resV1
,
nil
}
return
nil
,
err
}

GetRuleContentV2 provides single rule for api v2

func
GetRuleContentV2
(
ruleID
ctypes
.
RuleID
)
(
*
types
.
RuleContentV2
,
error
)
{
res
,
err
:=
getRuleContent
(
ruleID
)
if
err
==
nil
{
resV2
:=
RuleContentToV2
(
res
)
return
&
resV2
,
nil
}
return
nil
,
err
}
func
getRuleContent
(
ruleID
ctypes
.
RuleID
)
(
*
ctypes
.
RuleContent
,
error
)
{

to be sure the data is there

	
err
:=
WaitForContentDirectoryToBeReady
(
)
if
err
!=
nil
{
return
nil
,
err
}
ruleID
=
ctypes
.
RuleID
(
strings
.
TrimSuffix
(
string
(
ruleID
)
,
dotReport
)
)
res
,
found
:=
rulesWithContentStorage
.
getRuleContent
(
ruleID
)
if
!
found
{
return
nil
,
&
utypes
.
ItemNotFoundError
{
ItemID
:
ruleID
}
}
return
res
,
nil
}
func
getEmptyRulesWithContentMap
(
)
*
RulesWithContentStorage
{
s
:=
RulesWithContentStorage
{
}
s
.
rules
=
make
(
map
[
types
.
RuleID
]
*
types
.
RuleContent
)
s
.
rulesWithContent
=
make
(
map
[
ruleIDAndErrorKey
]
*
types
.
RuleWithContent
)
s
.
recommendationsWithContent
=
make
(
map
[
ctypes
.
RuleID
]
*
types
.
RuleWithContent
)
s
.
internalRuleIDs
=
make
(
[
]
ctypes
.
RuleID
,
0
)
s
.
externalRuleIDs
=
make
(
[
]
ctypes
.
RuleID
,
0
)
return
&
s
}

GetRuleIDs returns a list of rule IDs (rule modules)

func
GetRuleIDs
(
)
(
[
]
string
,
error
)
{
err
:=
WaitForContentDirectoryToBeReady
(
)
if
err
!=
nil
{
return
nil
,
err
}
return
rulesWithContentStorage
.
GetRuleIDs
(
)
,
nil
}

GetInternalRuleIDs returns a list of composite rule IDs ("| format") of internal rules

func
GetInternalRuleIDs
(
)
(
[
]
ctypes
.
RuleID
,
error
)
{
err
:=
WaitForContentDirectoryToBeReady
(
)
if
err
!=
nil
{
return
nil
,
err
}
return
rulesWithContentStorage
.
GetInternalRuleIDs
(
)
,
nil
}

GetExternalRuleIDs returns a list of composite rule IDs ("| format") of external rules

func
GetExternalRuleIDs
(
)
(
[
]
ctypes
.
RuleID
,
error
)
{
err
:=
WaitForContentDirectoryToBeReady
(
)
if
err
!=
nil
{
return
nil
,
err
}
return
rulesWithContentStorage
.
GetExternalRuleIDs
(
)
,
nil
}

GetExternalRuleSeverities returns a map of rule IDs and their severity (total risk), along with a list of unique severities

func
GetExternalRuleSeverities
(
)
(
map
[
ctypes
.
RuleID
]
int
,
[
]
int
,
error
,
)
{
err
:=
WaitForContentDirectoryToBeReady
(
)
if
err
!=
nil
{
return
nil
,
nil
,
err
}
severityMap
,
uniqueSeverities
:=
rulesWithContentStorage
.
GetExternalRuleSeverities
(
)
return
severityMap
,
uniqueSeverities
,
nil
}

GetExternalRulesManagedInfo returns a map of rule IDs and the information whether a rule is managed (has osd_customer tag) or not

func
GetExternalRulesManagedInfo
(
)
(
map
[
ctypes
.
RuleID
]
bool
,
error
,
)
{
err
:=
WaitForContentDirectoryToBeReady
(
)
if
err
!=
nil
{
return
nil
,
err
}
managedMap
:=
rulesWithContentStorage
.
GetExternalRulesManagedInfo
(
)
return
managedMap
,
nil
}

GetAllContentV1 returns content for all the loaded rules.

func
GetAllContentV1
(
)
(
[
]
types
.
RuleContentV1
,
error
)
{

to be sure the data is there

	
err
:=
WaitForContentDirectoryToBeReady
(
)
if
err
!=
nil
{
return
nil
,
err
}
return
rulesWithContentStorage
.
GetAllContentV1
(
)
,
nil
}

GetAllContentV2 returns content for api v2

func
GetAllContentV2
(
)
(
[
]
types
.
RuleContentV2
,
error
)
{

to be sure the data is there

	
err
:=
WaitForContentDirectoryToBeReady
(
)
if
err
!=
nil
{
return
nil
,
err
}
return
rulesWithContentStorage
.
GetAllContentV2
(
)
,
nil
}

RunUpdateContentLoop runs loop which updates rules content by ticker

func
RunUpdateContentLoop
(
servicesConf
services
.
Configuration
)
{
ticker
:=
time
.
NewTicker
(
servicesConf
.
GroupsPollingTime
)
for
{
UpdateContent
(
servicesConf
)
select
{
case
<-
ticker
.
C
:
case
<-
stopUpdateContentLoop
:
return
}
}
}

SetContentDirectoryTimeout sets the maximum duration for which the smart proxy waits if the content directory is empty

func
SetContentDirectoryTimeout
(
timeout
time
.
Duration
)
{
contentDirectoryTimeout
=
timeout
}

StopUpdateContentLoop stops the loop

func
StopUpdateContentLoop
(
)
{
stopUpdateContentLoop
<-
struct
{
}
{
}
}

UpdateContent function updates rule content

func
UpdateContent
(
servicesConf
services
.
Configuration
)
{
var
err
error
contentServiceDirectory
,
err
:=
services
.
GetContent
(
servicesConf
)
if
err
!=
nil
{
log
.
Error
(
)
.
Err
(
err
)
.
Msg
(
"Error retrieving static content"
)
return
}
SetRuleContentDirectory
(
contentServiceDirectory
)
err
=
WaitForContentDirectoryToBeReady
(
)
if
err
!=
nil
{
return
}
LoadRuleContent
(
ruleContentDirectory
)
}

FetchRuleContent - fetching content for particular rule Return values: - Structure with rules and content - return true if the rule has been filtered by OSDElegible field. False otherwise - return error if the one occurred during retrieval

func
FetchRuleContent
(
rule
*
ctypes
.
RuleOnReport
,
OSDEligible
bool
)
(
ruleWithContentResponse
*
types
.
RuleWithContentResponse
,
osdFiltered
bool
,
err
error
,
)
{
ruleID
:=
rule
.
Module
errorKey
:=
rule
.
ErrorKey
ruleWithContentResponse
=
nil
osdFiltered
=
false
ruleWithContent
,
err
:=
GetRuleWithErrorKeyContent
(
ruleID
,
errorKey
)
if
err
!=
nil
{
log
.
Warn
(
)
.
Err
(
err
)
.
Msgf
(
"unable to get content for rule with id %v and error key %v"
,
ruleID
,
errorKey
,
)
return
}
if
OSDEligible
&&
!
ruleWithContent
.
OSDCustomer
{
osdFiltered
=
true
return
}
ruleWithContentResponse
=
&
types
.
RuleWithContentResponse
{
CreatedAt
:
ruleWithContent
.
PublishDate
.
UTC
(
)
.
Format
(
time
.
RFC3339
)
,
Description
:
ruleWithContent
.
Description
,
ErrorKey
:
errorKey
,
Generic
:
ruleWithContent
.
Generic
,
Reason
:
ruleWithContent
.
Reason
,
Resolution
:
ruleWithContent
.
Resolution
,
MoreInfo
:
ruleWithContent
.
MoreInfo
,
TotalRisk
:
ruleWithContent
.
TotalRisk
,
RuleID
:
ruleID
,
TemplateData
:
rule
.
TemplateData
,
Tags
:
ruleWithContent
.
Tags
,
UserVote
:
rule
.
UserVote
,
Disabled
:
rule
.
Disabled
,
DisableFeedback
:
rule
.
DisableFeedback
,
DisabledAt
:
rule
.
DisabledAt
,
Internal
:
ruleWithContent
.
Internal
,
}
return
}