Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch cel #1369

Merged
merged 7 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions build/full/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ RUN curl -L https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-${JMETER_VER

ENV PATH /opt/apache-jmeter-${JMETER_VERSION}/bin/:$PATH


RUN curl -L https://github.com/mergestat/mergestat-lite/releases/download/v0.6.1/mergestat-linux-amd64.tar.gz -o mergestat.tar.gz && \
RUN curl -L https://github.com/flanksource/askgit/releases/download/v0.61.0-flanksource.1/mergestat-linux-amd64.tar.gz -o mergestat.tar.gz && \
tar zxf mergestat.tar.gz -C /usr/local/bin/ && \
rm mergestat.tar.gz

Expand Down
18 changes: 14 additions & 4 deletions chart/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ spec:
{{- else }}
value: "embedded:///opt/database/"
{{- end }}
{{- if .Values.upstream.enabled }}
{{- if .Values.upstream.secretKeyRef.name }}
envFrom:
- secretRef:
name: {{ .Values.upstream.secretKeyRef.name }}
Expand Down Expand Up @@ -140,11 +140,21 @@ spec:
{{- if gt (int .Values.replicas) 1 }}
- --enable-leader-election=true
{{- end }}
{{- range $k, $v := .Values.extraArgs}}
- --{{$k}}={{$v}}
{{- end }}

{{- if .Values.upstream.enabled }}
- --agent-name={{ .Values.upstream.agentName }}
- --upstream-host=$UPSTREAM_HOST
- --upstream-user=$UPSTREAM_USER
- --upstream-password=$UPSTREAM_PASSWORD
{{- if .Values.upstream.host }}
- --upstream-host={{ .Values.upstream.host }}
{{- end}}
{{- if .Values.upstream.user }}
- --upstream-user={{ .Values.upstream.user }}
{{- end}}
{{- if .Values.upstream.password }}
- --upstream-password={{ .Values.upstream.password }}
{{- end}}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
Expand Down
9 changes: 7 additions & 2 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,12 @@ data:
upstream:
enabled: false
agentName: default-agent
# Must contain: UPSTREAM_USER, UPSTREAM_PASS & UPSTREAM_HOST
host: ""
user: ""
password: ""
# Alternative to inlining values, secret must contain: UPSTREAM_NAME, UPSTREAM_USER, UPSTREAM_PASSWORD & UPSTREAM_HOST
secretKeyRef:
name: canary-checker-upstream
name:

ingress:
enabled: false
Expand Down Expand Up @@ -133,6 +136,8 @@ disableChecks: {}
# a list of db properties to update on startup
properties: {}

# a map of extra arguments to the canary-checker cli
extraArgs: {}
extra:
# nodeSelector:
# key: value
Expand Down
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
15 changes: 15 additions & 0 deletions checks/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ 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) {
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 +117,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 +155,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 +180,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
13 changes: 8 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ var Root = &cobra.Command{
if db.ConnectionString == "DB_URL" {
db.ConnectionString = ""
}

if canary.UpstreamConf.Valid() {
logger.Infof("Pushing checks to %s with name=%s user=%s", canary.UpstreamConf.Host, canary.UpstreamConf.AgentName, canary.UpstreamConf.Username)
}
},
}

Expand Down Expand Up @@ -65,13 +69,12 @@ func ServerFlags(flags *pflag.FlagSet) {
flags.IntVar(&db.CheckRetentionDays, "check-retention-period", db.DefaultCheckRetentionDays, "Check retention period in days")
flags.IntVar(&db.CanaryRetentionDays, "canary-retention-period", db.DefaultCanaryRetentionDays, "Canary retention period in days")

// Flags for push/pull
flags.StringVar(&canary.UpstreamConf.Host, "upstream-host", "", "central canary checker instance to push/pull canaries")
flags.StringVar(&canary.UpstreamConf.Username, "upstream-user", "", "upstream username")
flags.StringVar(&canary.UpstreamConf.Password, "upstream-password", "", "upstream password")
flags.StringVar(&canary.UpstreamConf.AgentName, "agent-name", "", "name of this agent")
flags.IntVar(&canary.ReconcilePageSize, "upstream-page-size", 500, "upstream reconciliation page size")
flags.DurationVar(&canary.ReconcileMaxAge, "upstream-max-age", time.Hour*48, "upstream reconciliation max age")
flags.StringVar(&canary.UpstreamConf.Host, "upstream-host", os.Getenv("UPSTREAM_HOST"), "central canary checker instance to push/pull canaries")
flags.StringVar(&canary.UpstreamConf.Username, "upstream-user", os.Getenv("UPSTREAM_USER"), "upstream username")
flags.StringVar(&canary.UpstreamConf.Password, "upstream-password", os.Getenv("UPSTREAM_PASSWORD"), "upstream password")
flags.StringVar(&canary.UpstreamConf.AgentName, "agent-name", os.Getenv("UPSTREAM_NAME"), "name of this agent")
}

func readFromEnv(v string) string {
Expand Down
41 changes: 39 additions & 2 deletions cmd/run.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"encoding/json"
"fmt"
"log"
"os"
Expand All @@ -22,7 +23,7 @@ import (
)

var outputFile, dataFile, runNamespace string
var junit, csv bool
var junit, csv, jsonExport bool

var Run = &cobra.Command{
Use: "run <canary.yaml>",
Expand Down Expand Up @@ -112,6 +113,21 @@ var Run = &cobra.Command{
logger.Fatalf("error writing output file: %v", err)
}
}
if jsonExport {

for _, result := range results {
result.Name = def(result.Name, result.Check.GetName(), result.Canary.Name)
result.Description = def(result.Description, result.Check.GetDescription())
result.Labels = merge(result.Check.GetLabels(), result.Labels)
fmt.Println(result.Name)
}

data, err := json.Marshal(results)
if err != nil {
logger.Fatalf("Failed to marshall json: %s", err)
}
_ = output.HandleOutput(string(data), outputFile)
}

logger.Infof("%d passed, %d failed in %s", passed, failed, timer)

Expand All @@ -121,11 +137,32 @@ var Run = &cobra.Command{
},
}

func merge(m1, m2 map[string]string) map[string]string {
out := make(map[string]string)
for k, v := range m1 {
out[k] = v
}
for k, v := range m2 {
out[k] = v
}
return out
}

func def(a ...string) string {
for _, s := range a {
if s != "" {
return s
}
}
return ""
}

func init() {
Run.PersistentFlags().StringVarP(&dataFile, "data", "d", "", "Template out each spec using the JSON or YAML data in this file")
Run.PersistentFlags().StringVarP(&outputFile, "output-file", "o", "", "file to output the results in")
Run.Flags().StringVarP(&runNamespace, "namespace", "n", "", "Namespace to run canary checks in")
Run.Flags().BoolVarP(&junit, "junit", "j", false, "output results in junit format")
Run.Flags().BoolVar(&junit, "junit", false, "output results in junit format")
Run.Flags().BoolVarP(&jsonExport, "json", "j", false, "output results in json format")
Run.Flags().BoolVar(&csv, "csv", false, "output results in csv format")
}

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()
Loading
Loading