Skip to content

Commit

Permalink
draft: new kubernetes resource check
Browse files Browse the repository at this point in the history
[skip ci]
  • Loading branch information
adityathebe committed Apr 1, 2024
1 parent 085f91b commit 6c44678
Show file tree
Hide file tree
Showing 11 changed files with 901 additions and 41 deletions.
86 changes: 45 additions & 41 deletions api/v1/canary_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,47 +43,48 @@ type CanarySpec struct {
//+optional
Replicas int `yaml:"replicas,omitempty" json:"replicas,omitempty"`

Env map[string]VarSource `yaml:"env,omitempty" json:"env,omitempty"`
HTTP []HTTPCheck `yaml:"http,omitempty" json:"http,omitempty"`
DNS []DNSCheck `yaml:"dns,omitempty" json:"dns,omitempty"`
DockerPull []DockerPullCheck `yaml:"docker,omitempty" json:"docker,omitempty"`
DockerPush []DockerPushCheck `yaml:"dockerPush,omitempty" json:"dockerPush,omitempty"`
ContainerdPull []ContainerdPullCheck `yaml:"containerd,omitempty" json:"containerd,omitempty"`
ContainerdPush []ContainerdPushCheck `yaml:"containerdPush,omitempty" json:"containerdPush,omitempty"`
S3 []S3Check `yaml:"s3,omitempty" json:"s3,omitempty"`
TCP []TCPCheck `yaml:"tcp,omitempty" json:"tcp,omitempty"`
Pod []PodCheck `yaml:"pod,omitempty" json:"pod,omitempty"`
LDAP []LDAPCheck `yaml:"ldap,omitempty" json:"ldap,omitempty"`
ICMP []ICMPCheck `yaml:"icmp,omitempty" json:"icmp,omitempty"`
Postgres []PostgresCheck `yaml:"postgres,omitempty" json:"postgres,omitempty"`
Mssql []MssqlCheck `yaml:"mssql,omitempty" json:"mssql,omitempty"`
Mysql []MysqlCheck `yaml:"mysql,omitempty" json:"mysql,omitempty"`
Restic []ResticCheck `yaml:"restic,omitempty" json:"restic,omitempty"`
Jmeter []JmeterCheck `yaml:"jmeter,omitempty" json:"jmeter,omitempty"`
Junit []JunitCheck `yaml:"junit,omitempty" json:"junit,omitempty"`
Helm []HelmCheck `yaml:"helm,omitempty" json:"helm,omitempty"`
Namespace []NamespaceCheck `yaml:"namespace,omitempty" json:"namespace,omitempty"`
Redis []RedisCheck `yaml:"redis,omitempty" json:"redis,omitempty"`
EC2 []EC2Check `yaml:"ec2,omitempty" json:"ec2,omitempty"`
Prometheus []PrometheusCheck `yaml:"prometheus,omitempty" json:"prometheus,omitempty"`
MongoDB []MongoDBCheck `yaml:"mongodb,omitempty" json:"mongodb,omitempty"`
CloudWatch []CloudWatchCheck `yaml:"cloudwatch,omitempty" json:"cloudwatch,omitempty"`
GitHub []GitHubCheck `yaml:"github,omitempty" json:"github,omitempty"`
GitProtocol []GitProtocolCheck `yaml:"gitProtocol,omitempty" json:"gitProtocol,omitempty"`
Kubernetes []KubernetesCheck `yaml:"kubernetes,omitempty" json:"kubernetes,omitempty"`
Folder []FolderCheck `yaml:"folder,omitempty" json:"folder,omitempty"`
Exec []ExecCheck `yaml:"exec,omitempty" json:"exec,omitempty"`
AwsConfig []AwsConfigCheck `yaml:"awsConfig,omitempty" json:"awsConfig,omitempty"`
AwsConfigRule []AwsConfigRuleCheck `yaml:"awsConfigRule,omitempty" json:"awsConfigRule,omitempty"`
DatabaseBackup []DatabaseBackupCheck `yaml:"databaseBackup,omitempty" json:"databaseBackup,omitempty"`
ConfigDB []ConfigDBCheck `yaml:"configDB,omitempty" json:"configDB,omitempty"`
Catalog []CatalogCheck `yaml:"catalog,omitempty" json:"catalog,omitempty"`
Opensearch []OpenSearchCheck `yaml:"opensearch,omitempty" json:"opensearch,omitempty"`
Elasticsearch []ElasticsearchCheck `yaml:"elasticsearch,omitempty" json:"elasticsearch,omitempty"`
AlertManager []AlertManagerCheck `yaml:"alertmanager,omitempty" json:"alertmanager,omitempty"`
Dynatrace []DynatraceCheck `yaml:"dynatrace,omitempty" json:"dynatrace,omitempty"`
AzureDevops []AzureDevopsCheck `yaml:"azureDevops,omitempty" json:"azureDevops,omitempty"`
Webhook *WebhookCheck `yaml:"webhook,omitempty" json:"webhook,omitempty"`
Env map[string]VarSource `yaml:"env,omitempty" json:"env,omitempty"`
HTTP []HTTPCheck `yaml:"http,omitempty" json:"http,omitempty"`
DNS []DNSCheck `yaml:"dns,omitempty" json:"dns,omitempty"`
DockerPull []DockerPullCheck `yaml:"docker,omitempty" json:"docker,omitempty"`
DockerPush []DockerPushCheck `yaml:"dockerPush,omitempty" json:"dockerPush,omitempty"`
ContainerdPull []ContainerdPullCheck `yaml:"containerd,omitempty" json:"containerd,omitempty"`
ContainerdPush []ContainerdPushCheck `yaml:"containerdPush,omitempty" json:"containerdPush,omitempty"`
S3 []S3Check `yaml:"s3,omitempty" json:"s3,omitempty"`
TCP []TCPCheck `yaml:"tcp,omitempty" json:"tcp,omitempty"`
Pod []PodCheck `yaml:"pod,omitempty" json:"pod,omitempty"`
LDAP []LDAPCheck `yaml:"ldap,omitempty" json:"ldap,omitempty"`
ICMP []ICMPCheck `yaml:"icmp,omitempty" json:"icmp,omitempty"`
Postgres []PostgresCheck `yaml:"postgres,omitempty" json:"postgres,omitempty"`
Mssql []MssqlCheck `yaml:"mssql,omitempty" json:"mssql,omitempty"`
Mysql []MysqlCheck `yaml:"mysql,omitempty" json:"mysql,omitempty"`
Restic []ResticCheck `yaml:"restic,omitempty" json:"restic,omitempty"`
Jmeter []JmeterCheck `yaml:"jmeter,omitempty" json:"jmeter,omitempty"`
Junit []JunitCheck `yaml:"junit,omitempty" json:"junit,omitempty"`
Helm []HelmCheck `yaml:"helm,omitempty" json:"helm,omitempty"`
Namespace []NamespaceCheck `yaml:"namespace,omitempty" json:"namespace,omitempty"`
Redis []RedisCheck `yaml:"redis,omitempty" json:"redis,omitempty"`
EC2 []EC2Check `yaml:"ec2,omitempty" json:"ec2,omitempty"`
Prometheus []PrometheusCheck `yaml:"prometheus,omitempty" json:"prometheus,omitempty"`
MongoDB []MongoDBCheck `yaml:"mongodb,omitempty" json:"mongodb,omitempty"`
CloudWatch []CloudWatchCheck `yaml:"cloudwatch,omitempty" json:"cloudwatch,omitempty"`
GitHub []GitHubCheck `yaml:"github,omitempty" json:"github,omitempty"`
GitProtocol []GitProtocolCheck `yaml:"gitProtocol,omitempty" json:"gitProtocol,omitempty"`
Kubernetes []KubernetesCheck `yaml:"kubernetes,omitempty" json:"kubernetes,omitempty"`
KubernetesResource []KubernetesResourceCheck `yaml:"kubernetesResource,omitempty" json:"kubernetesResource,omitempty"`
Folder []FolderCheck `yaml:"folder,omitempty" json:"folder,omitempty"`
Exec []ExecCheck `yaml:"exec,omitempty" json:"exec,omitempty"`
AwsConfig []AwsConfigCheck `yaml:"awsConfig,omitempty" json:"awsConfig,omitempty"`
AwsConfigRule []AwsConfigRuleCheck `yaml:"awsConfigRule,omitempty" json:"awsConfigRule,omitempty"`
DatabaseBackup []DatabaseBackupCheck `yaml:"databaseBackup,omitempty" json:"databaseBackup,omitempty"`
ConfigDB []ConfigDBCheck `yaml:"configDB,omitempty" json:"configDB,omitempty"`
Catalog []CatalogCheck `yaml:"catalog,omitempty" json:"catalog,omitempty"`
Opensearch []OpenSearchCheck `yaml:"opensearch,omitempty" json:"opensearch,omitempty"`
Elasticsearch []ElasticsearchCheck `yaml:"elasticsearch,omitempty" json:"elasticsearch,omitempty"`
AlertManager []AlertManagerCheck `yaml:"alertmanager,omitempty" json:"alertmanager,omitempty"`
Dynatrace []DynatraceCheck `yaml:"dynatrace,omitempty" json:"dynatrace,omitempty"`
AzureDevops []AzureDevopsCheck `yaml:"azureDevops,omitempty" json:"azureDevops,omitempty"`
Webhook *WebhookCheck `yaml:"webhook,omitempty" json:"webhook,omitempty"`
// interval (in seconds) to run checks on Deprecated in favor of Schedule
Interval uint64 `yaml:"interval,omitempty" json:"interval,omitempty"`
// Schedule to run checks on. Supports all cron expression, example: '30 3-6,20-23 * * *'. For more info about cron expression syntax see https://en.wikipedia.org/wiki/Cron
Expand Down Expand Up @@ -178,6 +179,9 @@ func (spec CanarySpec) GetAllChecks() []external.Check {
for _, check := range spec.Kubernetes {
checks = append(checks, check)
}
for _, check := range spec.KubernetesResource {
checks = append(checks, check)
}
for _, check := range spec.Folder {
checks = append(checks, check)
}
Expand Down
42 changes: 42 additions & 0 deletions api/v1/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/flanksource/duty/models"
"github.com/flanksource/duty/types"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

const (
Expand Down Expand Up @@ -776,6 +777,47 @@ func (c ConfigDBCheck) GetEndpoint() string {
return c.Query
}

type KubernetesGenericCheckTestResultSelector struct {
Path string `json:"path,omitempty"`
Type string `json:"type,omitempty"`
PodSelector string `json:"podSelector,omitempty"`
}

type KubernetesResourceCheck struct {
Description `yaml:",inline" json:",inline"`
Templatable `yaml:",inline" json:",inline"`

// StaticResources are kubernetes resources that are created & only
// cleared when the canary is deleted
// +kubebuilder:validation:Schemaless
// +kubebuilder:pruning:PreserveUnknownFields
StaticResources []unstructured.Unstructured `json:"staticResources,omitempty"`

// Resources are kubernetes resources that are created & cleared
// after every check run.
// +kubebuilder:validation:Schemaless
// +kubebuilder:pruning:PreserveUnknownFields
Resources []unstructured.Unstructured `json:"resources"`

// Checks to run agains the kubernetes resources
// Checks []CanarySpec `json:"checks,omitempty"`

// Kubeconfig is the kubeconfig or the path to the kubeconfig file.
Kubeconfig *types.EnvVar `yaml:"kubeconfig,omitempty" json:"kubeconfig,omitempty"`

WaitForReady bool `json:"waitForReady,omitempty"`
Timeout string `json:"timeout,omitempty"`
TestResults []KubernetesGenericCheckTestResultSelector `json:"testResults,omitempty"`
}

func (c KubernetesResourceCheck) GetType() string {
return "kubernetes_resource"
}

func (c KubernetesResourceCheck) GetEndpoint() string {
return "" // TODO:
}

type ResourceSelector struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
LabelSelector string `json:"labelSelector,omitempty" yaml:"labelSelector,omitempty"`
Expand Down
64 changes: 64 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions checks/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ var All = []Checker{
&JmeterChecker{},
&JunitChecker{},
&KubernetesChecker{},
&KubernetesResourceChecker{},
&LdapChecker{},
&MongoDBChecker{},
&MssqlChecker{},
Expand Down
106 changes: 106 additions & 0 deletions checks/kubernetes_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package checks

import (
"fmt"
"strings"

"github.com/flanksource/canary-checker/api/context"
v1 "github.com/flanksource/canary-checker/api/v1"
"github.com/flanksource/canary-checker/pkg"
"github.com/flanksource/commons/logger"
"github.com/flanksource/duty/types"
)

// maximum number of static & non static resources a canary can have
const maxResourcesAllowed = 10
const annotationkey = "flanksource.canary-checker/kubernetes-resource-canary"

type KubernetesResourceChecker struct{}

func (c *KubernetesResourceChecker) Type() string {
return "kubernetes_resource"
}

func (c *KubernetesResourceChecker) Run(ctx *context.Context) pkg.Results {
var results pkg.Results
for _, conf := range ctx.Canary.Spec.KubernetesResource {
results = append(results, c.Check(ctx, conf)...)
}
return results
}

func (c *KubernetesResourceChecker) applyKubeconfig(ctx *context.Context, kubeConfig types.EnvVar) error {
val, err := ctx.GetEnvValueFromCache(kubeConfig)
if err != nil {
return fmt.Errorf("failed to get kubeconfig from env: %w", err)
}

if strings.HasPrefix(val, "/") {
kClient, kube, err := pkg.NewKommonsClientWithConfigPath(val)
if err != nil {
return fmt.Errorf("failed to initialize kubernetes client from the provided kubeconfig: %w", err)
}

ctx = ctx.WithDutyContext(ctx.WithKommons(kClient))
ctx = ctx.WithDutyContext(ctx.WithKubernetes(kube))
} else {
kClient, kube, err := pkg.NewKommonsClientWithConfig(val)
if err != nil {
return fmt.Errorf("failed to initialize kubernetes client from the provided kubeconfig: %w", err)
}

ctx = ctx.WithDutyContext(ctx.WithKommons(kClient))
ctx = ctx.WithDutyContext(ctx.WithKubernetes(kube))
}

return nil
}

func (c *KubernetesResourceChecker) Check(ctx *context.Context, check v1.KubernetesResourceCheck) pkg.Results {
result := pkg.Success(check, ctx.Canary)
var results pkg.Results
results = append(results, result)

totalResources := len(check.StaticResources) + len(check.Resources)
if totalResources > maxResourcesAllowed {
return results.Failf("too many resources (%d). only %d allowed", totalResources, maxResourcesAllowed)
}

if check.Kubeconfig != nil {
if err := c.applyKubeconfig(ctx, *check.Kubeconfig); err != nil {
return results.Failf("failed to apply kube config: %v", err)
}
}

for i := range check.StaticResources {
resource := check.StaticResources[i]

// annotate the resource with the canary ID so we can easily clean it up later
resource.SetAnnotations(map[string]string{annotationkey: ctx.Canary.ID()})
if err := ctx.Kommons().ApplyUnstructured(ctx.Namespace, &resource); err != nil {
return results.Failf("failed to apply static resource %s: %v", resource.GetName(), err)
}
}

for i := range check.Resources {
resource := check.Resources[i]
resource.SetAnnotations(map[string]string{annotationkey: ctx.Canary.ID()})
if err := ctx.Kommons().ApplyUnstructured(ctx.Namespace, &resource); err != nil {
return results.Failf("failed to apply resource %s: %v", resource.GetName(), err)
}

defer func() {
if err := ctx.Kommons().DeleteUnstructured(ctx.Namespace, &resource); err != nil {
logger.Errorf("failed to delete resource %s: %v", resource.GetName(), err)
}
}()
}

if check.WaitForReady {
logger.Infof("waiting for resources to be ready.")
}

// run the actual check now

return nil
}
Loading

0 comments on commit 6c44678

Please sign in to comment.