main.go

Copyright 2020 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
main
import
(
"flag"
"fmt"
"strings"
"github.com/RedHatInsights/insights-operator-utils/logger"
"github.com/rs/zerolog/log"
"github.com/RedHatInsights/insights-content-service/content"
"github.com/RedHatInsights/insights-content-service/groups"
)

groupConfigMap is a shorthand for the map used to store the group configuration.

type
groupConfigMap
map
[
string
]
groups
.
Group
var
(
groupConfigPath
=
"./groups_config.yaml"
contentDirPath
=
"./content/"
)
func
init
(
)
{
flag
.
StringVar
(
&
groupConfigPath
,
"config"
,
groupConfigPath
,
"Path to the group configuration YAML file."
)
flag
.
StringVar
(
&
contentDirPath
,
"content"
,
contentDirPath
,
"Path to the content directory (the one containing the 'config.yaml' file)."
)
flag
.
Parse
(
)
}
func
main
(
)
{
initLogger
(
)
groupCfg
:=
checkGroupConfig
(
)
checkRuleContent
(
groupCfg
)
}

initLogger initializes the zerolog library to pretty-print the log messages.

func
initLogger
(
)
{
err
:=
logger
.
InitZerolog
(
logger
.
LoggingConfiguration
{
Debug
:
true
,
LogLevel
:
"debug"
,
LoggingToCloudWatchEnabled
:
false
,
}
,
logger
.
CloudWatchConfiguration
{
}
,
logger
.
SentryLoggingConfiguration
{
}
,
logger
.
KafkaZerologConfiguration
{
}
,
)
if
err
!=
nil
{
log
.
Fatal
(
)
.
Err
(
err
)
.
Msg
(
"unable to initialize zerolog"
)
}
}

checkGroupConfig reads the group configuration file and performs defined checks on it. Then it returns the config to be used by the rule content checks.

func
checkGroupConfig
(
)
groupConfigMap
{
groupCfg
,
err
:=
groups
.
ParseGroupConfigFile
(
groupConfigPath
)
if
err
!=
nil
{
log
.
Fatal
(
)
.
Err
(
err
)
.
Msg
(
"unable to parse group config file"
)
}

Unique group is just a check that makes sure no two groups have the same name property.

	
uniqueGroups
:=
map
[
string
]
string
{
}

For each group defined in the group configuration file.

	
for
groupKey
,
group
:=
range
groupCfg
{
if
firstGroupKey
,
exists
:=
uniqueGroups
[
group
.
Name
]
;
exists
{
log
.
Warn
(
)
.
Msgf
(
"multiple groups with the name '%s' (first with key '%s', but also with key '%s')"
,
group
.
Name
,
firstGroupKey
,
groupKey
)
}
else
{
uniqueGroups
[
group
.
Name
]
=
groupKey
}

Check for duplicate tag in a single group. The same tag being used by multiple groups is allowed.

		
uniqueTags
:=
map
[
string
]
struct
{
}
{
}

For each tag assigned to the group.

		
for
_
,
tag
:=
range
group
.
Tags
{
if
_
,
exists
:=
uniqueTags
[
tag
]
;
exists
{
log
.
Warn
(
)
.
Str
(
"tag"
,
tag
)
.
Str
(
"group"
,
group
.
Name
)
.
Msg
(
"duplicate tag reference in group"
)
}
else
{
uniqueTags
[
tag
]
=
struct
{
}
{
}
}
}
}
return
groupCfg
}

checkRuleContent checks if rule content files are not empty and if the tags assigned to all error codes really exist. Proper checking of the rule content is done in the ccx-ocp-rules repo itself, we are trusting the tests and checks in that repo, most of these warnings are triggered by internal rules and are only here in case something went wrong in those tests. We could add separate checks for internal/external but it's better to do it in the ocp-rules repo.

func
checkRuleContent
(
groupCfg
groupConfigMap
)
{
ruleContentDir
,
_
,
err
:=
content
.
ParseRuleContentDir
(
contentDirPath
)
if
err
!=
nil
{
log
.
Fatal
(
)
.
Err
(
err
)
.
Msg
(
"unable to rule content directory"
)
}

For every rule with a content available.

	
for
ruleName
,
ruleContent
:=
range
ruleContentDir
.
Rules
{
checkRuleAttributeNotEmpty
(
ruleName
,
"name"
,
ruleContent
.
Plugin
.
Name
)
checkRuleAttributeNotEmpty
(
ruleName
,
"node_id"
,
ruleContent
.
Plugin
.
NodeID
)
checkRuleAttributeNotEmpty
(
ruleName
,
"product_code"
,
ruleContent
.
Plugin
.
ProductCode
)
checkRuleAttributeNotEmpty
(
ruleName
,
"python_module"
,
ruleContent
.
Plugin
.
PythonModule
)
checkRuleFileNotEmpty
(
ruleName
,
"summary.md"
,
ruleContent
.
Summary
)
if
len
(
ruleContent
.
ErrorKeys
)
==
0
{
log
.
Warn
(
)
.
Str
(
"rule"
,
ruleName
)
.
Msg
(
"rule contains no error code"
)
}

For every error code of the rule.

		
for
errCode
,
errContent
:=
range
ruleContent
.
ErrorKeys
{
checkErrorCodeAttributeNotEmpty
(
ruleName
,
errCode
,
"description"
,
errContent
.
Metadata
.
Description
)
checkErrorCodeAttributeNotEmpty
(
ruleName
,
errCode
,
"impact"
,
errContent
.
Metadata
.
Impact
.
Name
)
checkErrorCodeAttributeNotEmpty
(
ruleName
,
errCode
,
"likelihood"
,
fmt
.
Sprint
(
errContent
.
Metadata
.
Likelihood
)
)
checkErrorCodeAttributeNotEmpty
(
ruleName
,
errCode
,
"publish_date"
,
errContent
.
Metadata
.
PublishDate
)
checkErrorCodeAttributeNotEmpty
(
ruleName
,
errCode
,
"status"
,
errContent
.
Metadata
.
Status
)
checkErrorCodeAttributeNotEmpty
(
ruleName
,
errCode
,
"resolution_risk"
,
errContent
.
Metadata
.
Status
)
checkErrorCodeFileNotEmpty
(
ruleName
,
errCode
,
"generic.md"
,
errContent
.
Generic
)
checkErrorCodeTags
(
groupCfg
,
ruleName
,
errCode
,
errContent
)
}
}
}

checkErrorCodeTags checks that the tags referenced by the error code are valid. At the end, all assigned tags (and the groups they belong to) are printed in the form of a map.

func
checkErrorCodeTags
(
groupCfg
groupConfigMap
,
ruleName
,
errCode
string
,
errContent
content
.
RuleErrorKeyContent
)
{
errGroups
:=
map
[
string
]
[
]
string
{
}

For every tag of that error code.

	
for
_
,
errTag
:=
range
errContent
.
Metadata
.
Tags
{

Check for duplicate tags in the error code's content.

		
if
_
,
exists
:=
errGroups
[
errTag
]
;
exists
{
log
.
Error
(
)
.
Msgf
(
"duplicate tag '%s' in content of '%s|%s'"
,
errTag
,
ruleName
,
errCode
)
}

List of groups to which the tag belongs.

		
tagGroups
:=
[
]
string
{
}

Find a group with the tag.

		
for
_
,
group
:=
range
groupCfg
{
for
_
,
tag
:=
range
group
.
Tags
{
if
tag
==
errTag
{
tagGroups
=
append
(
tagGroups
,
group
.
Name
)
break
}
}
}

Check if at least one group with the tag was found.

		
if
len
(
tagGroups
)
>
0
{
errGroups
[
errTag
]
=
tagGroups
}
else
{
log
.
Error
(
)
.
Msgf
(
"unknown tag '%s' in content of '%s|%s'"
,
errTag
,
ruleName
,
errCode
)
}
}
log
.
Info
(
)
.
Msgf
(
"%s|%s: %v"
,
ruleName
,
errCode
,
errGroups
)
}

Base rule content checks.


func
checkRuleFileNotEmpty
(
ruleName
,
fileName
,
value
string
)
{
checkStringNotEmpty
(
fmt
.
Sprintf
(
"content file '%s' of rule '%s'"
,
fileName
,
ruleName
)
,
value
,
)
}
func
checkRuleAttributeNotEmpty
(
ruleName
,
attribName
,
value
string
)
{
checkStringNotEmpty
(
fmt
.
Sprintf
(
"attribute '%s' of rule '%s'"
,
attribName
,
ruleName
)
,
value
,
)
}

Error code content checks.


func
checkErrorCodeFileNotEmpty
(
ruleName
,
errorCode
,
fileName
,
value
string
)
{
checkStringNotEmpty
(
fmt
.
Sprintf
(
"content file '%s' of error code '%s|%s'"
,
fileName
,
ruleName
,
errorCode
)
,
value
,
)
}
func
checkErrorCodeAttributeNotEmpty
(
ruleName
,
errorCode
,
attribName
,
value
string
)
{
checkStringNotEmpty
(
fmt
.
Sprintf
(
"attribute '%s' of error code '%s|%s'"
,
attribName
,
ruleName
,
errorCode
)
,
value
,
)
}

Generic check for any name:value string pair.

func
checkStringNotEmpty
(
name
,
value
string
)
{
if
strings
.
TrimSpace
(
value
)
==
""
{
log
.
Warn
(
)
.
Str
(
"name"
,
name
)
.
Msg
(
"value is empty"
)
}
}