Skip to content

Commit

Permalink
chore: switch to cel for alertmanager / aws checks
Browse files Browse the repository at this point in the history
  • Loading branch information
moshloop committed Oct 22, 2023
1 parent 7b12271 commit 2134686
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 75 deletions.
8 changes: 6 additions & 2 deletions checks/alertmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ func (c *AlertManagerChecker) Check(ctx *context.Context, extConfig external.Che
return results
}

var alertMessages []map[string]any
type Alerts struct {
Alerts []map[string]interface{} `json:"alerts,omitempty"`
}

var alertMessages []map[string]interface{}
for _, alert := range alerts.Payload {
alertMap := map[string]any{
"name": generateFullName(alert.Labels["alertname"], alert.Labels),
Expand All @@ -83,7 +87,7 @@ func (c *AlertManagerChecker) Check(ctx *context.Context, extConfig external.Che
alertMessages = append(alertMessages, alertMap)
}

result.AddDetails(alertMessages)
result.AddDetails(Alerts{Alerts: alertMessages})
return results
}

Expand Down
100 changes: 69 additions & 31 deletions checks/aws_config_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
package checks

import (
"fmt"
"strings"
"time"

"github.com/aws/aws-sdk-go-v2/service/configservice"
"github.com/aws/aws-sdk-go-v2/service/configservice/types"
Expand Down Expand Up @@ -65,44 +65,82 @@ func (c *AwsConfigRuleChecker) Check(ctx *context.Context, extConfig external.Ch
if err != nil {
return results.Failf("failed to describe compliance rules: %v", err)
}
var complianceResults pkg.Results

type ConfigRuleResource struct {
ID string `json:"id"`
Annotation string `json:"annotation"`
Type string `json:"type"`
Recorded time.Time `json:"recorded"`
Mode string `json:"mode"`
}

type ComplianceResult struct {

// Supplementary information about how the evaluation determined the compliance.
Annotation string `json:"annotation"`

ConfigRule string `json:"rule"`

Description string `json:"description"`
// Indicates whether the Amazon Web Services resource complies with the Config
// rule that evaluated it. For the EvaluationResult data type, Config supports
// only the COMPLIANT , NON_COMPLIANT , and NOT_APPLICABLE values. Config does not
// support the INSUFFICIENT_DATA value for the EvaluationResult data type.
ComplianceType string `json:"type"`

Resources []ConfigRuleResource `json:"resources"`
}

var complianceResults []ComplianceResult
var failures []string
for _, complianceRule := range output.ComplianceByConfigRules {
if configRuleInRules(check.IgnoreRules, *complianceRule.ConfigRuleName) || complianceRule.Compliance.ComplianceType == "INSUFFICIENT_DATA" || complianceRule.Compliance.ComplianceType == "NOT_APPLICABLE" {
continue
}
if complianceRule.Compliance != nil {
var complianceResult *pkg.CheckResult
complianceCheck := check
complianceCheck.Description.Description = fmt.Sprintf("%s - checking compliance for config rule: %s", check.Description.Description, *complianceRule.ConfigRuleName)
if complianceRule.Compliance.ComplianceType != "COMPLIANT" {
complianceResult = pkg.Fail(complianceCheck, ctx.Canary)
complianceDetailsOutput, err := client.GetComplianceDetailsByConfigRule(ctx, &configservice.GetComplianceDetailsByConfigRuleInput{
ComplianceTypes: []types.ComplianceType{
"NON_COMPLIANT",
},
ConfigRuleName: complianceRule.ConfigRuleName,

if complianceRule.Compliance == nil {
continue
}
var data = ComplianceResult{
ConfigRule: *complianceRule.ConfigRuleName,
ComplianceType: string(complianceRule.Compliance.ComplianceType),
}

if complianceRule.Compliance.ComplianceType != "COMPLIANT" {
failures = append(failures, *complianceRule.ConfigRuleName)
complianceDetailsOutput, err := client.GetComplianceDetailsByConfigRule(ctx, &configservice.GetComplianceDetailsByConfigRuleInput{
ComplianceTypes: []types.ComplianceType{
"NON_COMPLIANT",
},
ConfigRuleName: complianceRule.ConfigRuleName,
})
if err != nil {
result.Failf("failed to get compliance details: %v", err)
continue
}
for _, result := range complianceDetailsOutput.EvaluationResults {
id := *result.EvaluationResultIdentifier.EvaluationResultQualifier
data.Resources = append(data.Resources, ConfigRuleResource{
ID: *id.ResourceId,
Type: *id.ResourceType,
Mode: string(id.EvaluationMode),
Recorded: *result.ResultRecordedTime,
Annotation: *result.Annotation,
})
if err != nil {
complianceResult.Failf("failed to get compliance details: %v", err)
continue
}
var resources []string
for _, result := range complianceDetailsOutput.EvaluationResults {
if result.EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId != nil {
resources = append(resources, *result.EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId)
}
}
complianceResult.AddDetails(resources)
complianceResult.ResultMessage(strings.Join(resources, ","))
} else {
complianceResult = pkg.Success(complianceCheck, ctx.Canary)
complianceResult.AddDetails(complianceRule)
complianceResult.ResultMessage(fmt.Sprintf("%s rule is %v", *complianceRule.ConfigRuleName, complianceRule.Compliance.ComplianceType))
}
complianceResults = append(complianceResults, complianceResult)
}
complianceResults = append(complianceResults, data)
}
return complianceResults

if check.Test.IsEmpty() && len(failures) > 0 {
result.Failf(strings.Join(failures, ", "))
}
if r, err := unstructure(map[string]interface{}{"rules": complianceResults}); err != nil {
result.Failf(err.Error())
} else {
result.AddDetails(r)
}
return results
}

func configRuleInRules(rules []string, ruleName string) bool {
Expand Down
6 changes: 5 additions & 1 deletion checks/cloudwatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ func (c *CloudWatchChecker) Check(ctx *context.Context, extConfig external.Check
if err != nil {
return results.ErrorMessage(err)
}
result.AddDetails(alarms)
if o, err := unstructure(alarms); err != nil {
return results.ErrorMessage(err)
} else {
result.AddDetails(o)
}
firing := []string{}
for _, alarm := range alarms.MetricAlarms {
if alarm.StateValue == types.StateValueAlarm {
Expand Down
16 changes: 16 additions & 0 deletions checks/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ func def(a, b string) string {
return b
}

// unstructure marshalls a struct to and from JSON to remove any type details
func unstructure(o any) (out interface{}, err error) {

Check failure on line 55 in checks/common.go

View workflow job for this annotation

GitHub Actions / lint

unnecessary leading newline (whitespace)

data, err := json.Marshal(o)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &out)
return out, err
}

func template(ctx *context.Context, template v1.Template) (string, error) {
return gomplate.RunTemplate(ctx.Environment, template.Gomplate())
}
Expand Down Expand Up @@ -107,6 +118,7 @@ func transform(ctx *context.Context, in *pkg.CheckResult) ([]*pkg.CheckResult, e
// We use this label to set the transformed column to true
// This label is used and then removed in pkg.FromV1 function
r.Canary.Labels["transformed"] = "true" //nolint:goconst
r.Labels = t.Labels
r.Transformed = true
results = append(results, &r)
}
Expand Down Expand Up @@ -144,6 +156,9 @@ func transform(ctx *context.Context, in *pkg.CheckResult) ([]*pkg.CheckResult, e
if t.DisplayType != "" {
in.DisplayType = t.DisplayType
}
if len(t.Labels) > 0 {
in.Labels = t.Labels
}
if len(t.Data) > 0 {
for k, v := range t.Data {
in.Data[k] = v
Expand All @@ -166,6 +181,7 @@ func GetJunitReportFromResults(canaryName string, results []*pkg.CheckResult) Ju
test.Name = result.Check.GetDescription()
test.Message = result.Message
test.Duration = float64(result.Duration) / 1000
test.Properties = result.Labels
testSuite.Duration += float64(result.Duration) / 1000
if result.Pass {
testSuite.Passed++
Expand Down
20 changes: 16 additions & 4 deletions fixtures/aws/aws_config_rule_pass.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,19 @@ metadata:
spec:
interval: 30
awsConfigRule:
- description: Test config rules
name: non compliant rules
complianceTypes:
- NON_COMPLIANT
- name: AWS Config Rule
region: "eu-west-1"
complianceTypes: [NON_COMPLIANT]
transform:
expr: |
results.rules.map(i,
i.resources.map(r,
{
'name': i.rule + "/" + r.type + "/" + r.id,
'description': i.rule,
'icon': 'aws-config-alarm',
'duration': time.Since(timestamp(r.recorded)).getMilliseconds(),
'labels': {'id': r.id, 'type': r.type},
'message': i.description + i.annotation + r.annotation
})
).flatten().toJSON()
33 changes: 20 additions & 13 deletions fixtures/aws/cloudwatch_pass.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
cloudwatch:
- accessKey:
valueFrom:
secretKeyRef:
key: aws
name: access-key
secretKey:
valueFrom:
secretKeyRef:
key: aws
name: secrey-key
region: "us-east-1"
#skipTLSVerify: true
apiVersion: canaries.flanksource.com/v1
kind: Canary
metadata:
name: cloudwatch
spec:
interval: 30
cloudwatch:
region: "eu-west-1"
transform:
expr: |
results.MetricAlarms.filter(i, i.StateValue != 'OK' ).map(i,
{
'name': i.MetricName,
'icon': 'aws-cloudwatch-alarm',
'duration': time.Since(timestamp(i.StateTransitionedTimestamp)).getMilliseconds(),
'labels': fromAWSMap(i.Dimensions).
merge({'arn': i.AlarmArn}),
'message': "%s (%s) %s".format([i.AlarmDescription, i.AlarmArn, fromAWSMap(i.Dimensions)]),
}
).toJSON()
26 changes: 11 additions & 15 deletions fixtures/datasources/alertmanager_fail.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,14 @@ spec:
- KubeScheduler.*
exclude_filters:
namespace: elastic-system
transform:
javascript: |
var out = _.map(results, function(r) {
return {
name: r.name,
labels: r.labels,
icon: 'alert',
message: r.message,
description: r.message,
status: 'unhealthy',
}
})
JSON.stringify(out);
test:
template: "true"
transform:
expr: |
results.alerts.map(r,
{
'name': r.name + r.fingerprint,
'labels': r.labels,
'icon': 'alert',
'message': r.message,
'description': r.message,
}
).toJSON()
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/fergusstrange/embedded-postgres v1.24.0
github.com/flanksource/commons v1.17.0
github.com/flanksource/duty v1.0.201
github.com/flanksource/gomplate/v3 v3.20.18
github.com/flanksource/gomplate/v3 v3.20.19
github.com/flanksource/is-healthy v0.0.0-20231003215854-76c51e3a3ff7
github.com/flanksource/kommons v0.31.4
github.com/flanksource/postq v1.0.0
Expand Down
10 changes: 2 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -818,8 +818,8 @@ github.com/flanksource/commons v1.17.0/go.mod h1:RDdQI0/QYC4GzicbDaXIvBPjWuQWKLz
github.com/flanksource/duty v1.0.201 h1:c8r02bfuF47E2svK+qXCLHKaSqOCZZHKPj+v54eimqc=
github.com/flanksource/duty v1.0.201/go.mod h1:aO1uXnT1eVtiIcicriK4brqJLmeXgbrYWtNR0H5IkLE=
github.com/flanksource/gomplate/v3 v3.20.4/go.mod h1:27BNWhzzSjDed1z8YShO6W+z6G9oZXuxfNFGd/iGSdc=
github.com/flanksource/gomplate/v3 v3.20.18 h1:qYiznMxhq+Zau5iWnVzW1yDzA1deHOsmo6yldCN7JhQ=
github.com/flanksource/gomplate/v3 v3.20.18/go.mod h1:2GgHZ2vWmtDspJMBfUIryOuzJSwc8jU7Kw9fDLr0TMA=
github.com/flanksource/gomplate/v3 v3.20.19 h1:xl+XMYWXtlrO6FfU+VxwjNwX4/oBK3/soOtHRvUt2us=
github.com/flanksource/gomplate/v3 v3.20.19/go.mod h1:2GgHZ2vWmtDspJMBfUIryOuzJSwc8jU7Kw9fDLr0TMA=
github.com/flanksource/is-healthy v0.0.0-20230705092916-3b4cf510c5fc/go.mod h1:4pQhmF+TnVqJroQKY8wSnSp+T18oLson6YQ2M0qPHfQ=
github.com/flanksource/is-healthy v0.0.0-20231003215854-76c51e3a3ff7 h1:s6jf6P1pRfdvksVFjIXFRfnimvEYUR0/Mmla1EIjiRM=
github.com/flanksource/is-healthy v0.0.0-20231003215854-76c51e3a3ff7/go.mod h1:BH5gh9JyEAuuWVP6Q5y9h43VozS0RfKyjNpM9L4v4hw=
Expand Down Expand Up @@ -2318,22 +2318,16 @@ k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg=
k8s.io/api v0.26.4/go.mod h1:WwKEXU3R1rgCZ77AYa7DFksd9/BAIKyOmRlbVxgvjCk=
k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw=
k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg=
k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM=
k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc=
k8s.io/apiextensions-apiserver v0.28.0 h1:CszgmBL8CizEnj4sj7/PtLGey6Na3YgWyGCPONv7E9E=
k8s.io/apiextensions-apiserver v0.28.0/go.mod h1:uRdYiwIuu0SyqJKriKmqEN2jThIJPhVmOWETm8ud1VE=
k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
k8s.io/apimachinery v0.26.4/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ=
k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU=
k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A=
k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8=
k8s.io/cli-runtime v0.28.0 h1:Tcz1nnccXZDNIzoH6EwjCs+7ezkUGhorzCweEvlVOFg=
k8s.io/cli-runtime v0.28.0/go.mod h1:U+ySmOKBm/JUCmebhmecXeTwNN1RzI7DW4+OM8Oryas=
k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY=
k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY=
k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4=
k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo=
k8s.io/component-base v0.28.1 h1:LA4AujMlK2mr0tZbQDZkjWbdhTV5bRyEyAFe0TJxlWg=
k8s.io/component-base v0.28.1/go.mod h1:jI11OyhbX21Qtbav7JkhehyBsIRfnO8oEgoAR12ArIU=
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
Expand Down

0 comments on commit 2134686

Please sign in to comment.