From 6c446785930d60953c0e95a22002966ca6734d3b Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Thu, 28 Mar 2024 16:31:16 +0545 Subject: [PATCH] draft: new kubernetes resource check [skip ci] --- api/v1/canary_types.go | 86 ++++++------- api/v1/checks.go | 42 +++++++ api/v1/zz_generated.deepcopy.go | 64 ++++++++++ checks/checker.go | 1 + checks/kubernetes_resource.go | 106 ++++++++++++++++ config/deploy/crd.yaml | 153 ++++++++++++++++++++++++ config/deploy/manifests.yaml | 153 ++++++++++++++++++++++++ config/schemas/canary.schema.json | 103 ++++++++++++++++ config/schemas/component.schema.json | 103 ++++++++++++++++ config/schemas/topology.schema.json | 103 ++++++++++++++++ fixtures/k8s/ingress_accessibility.yaml | 28 +++++ 11 files changed, 901 insertions(+), 41 deletions(-) create mode 100644 checks/kubernetes_resource.go create mode 100644 fixtures/k8s/ingress_accessibility.yaml diff --git a/api/v1/canary_types.go b/api/v1/canary_types.go index 5c28acbe1..4fc16166c 100644 --- a/api/v1/canary_types.go +++ b/api/v1/canary_types.go @@ -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 @@ -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) } diff --git a/api/v1/checks.go b/api/v1/checks.go index 041de33e2..edd5fa891 100644 --- a/api/v1/checks.go +++ b/api/v1/checks.go @@ -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 ( @@ -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"` diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 99541c1c6..c41ef5fe6 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -27,6 +27,7 @@ import ( "github.com/flanksource/duty/connection" "github.com/flanksource/duty/types" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) @@ -550,6 +551,13 @@ func (in *CanarySpec) DeepCopyInto(out *CanarySpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.KubernetesResource != nil { + in, out := &in.KubernetesResource, &out.KubernetesResource + *out = make([]KubernetesResourceCheck, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.Folder != nil { in, out := &in.Folder, &out.Folder *out = make([]FolderCheck, len(*in)) @@ -2251,6 +2259,62 @@ func (in *KubernetesCheck) DeepCopy() *KubernetesCheck { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubernetesGenericCheckTestResultSelector) DeepCopyInto(out *KubernetesGenericCheckTestResultSelector) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesGenericCheckTestResultSelector. +func (in *KubernetesGenericCheckTestResultSelector) DeepCopy() *KubernetesGenericCheckTestResultSelector { + if in == nil { + return nil + } + out := new(KubernetesGenericCheckTestResultSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubernetesResourceCheck) DeepCopyInto(out *KubernetesResourceCheck) { + *out = *in + in.Description.DeepCopyInto(&out.Description) + out.Templatable = in.Templatable + if in.StaticResources != nil { + in, out := &in.StaticResources, &out.StaticResources + *out = make([]unstructured.Unstructured, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]unstructured.Unstructured, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Kubeconfig != nil { + in, out := &in.Kubeconfig, &out.Kubeconfig + *out = new(types.EnvVar) + (*in).DeepCopyInto(*out) + } + if in.TestResults != nil { + in, out := &in.TestResults, &out.TestResults + *out = make([]KubernetesGenericCheckTestResultSelector, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesResourceCheck. +func (in *KubernetesResourceCheck) DeepCopy() *KubernetesResourceCheck { + if in == nil { + return nil + } + out := new(KubernetesResourceCheck) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LDAP) DeepCopyInto(out *LDAP) { *out = *in diff --git a/checks/checker.go b/checks/checker.go index 9bc61bb36..5fd373851 100644 --- a/checks/checker.go +++ b/checks/checker.go @@ -44,6 +44,7 @@ var All = []Checker{ &JmeterChecker{}, &JunitChecker{}, &KubernetesChecker{}, + &KubernetesResourceChecker{}, &LdapChecker{}, &MongoDBChecker{}, &MssqlChecker{}, diff --git a/checks/kubernetes_resource.go b/checks/kubernetes_resource.go new file mode 100644 index 000000000..4c60f6fea --- /dev/null +++ b/checks/kubernetes_resource.go @@ -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 +} diff --git a/config/deploy/crd.yaml b/config/deploy/crd.yaml index ad3367f53..a2d12d02f 100644 --- a/config/deploy/crd.yaml +++ b/config/deploy/crd.yaml @@ -4980,6 +4980,159 @@ spec: - name type: object type: array + kubernetesResource: + items: + properties: + description: + description: Description for the check + type: string + display: + properties: + expr: + type: string + javascript: + type: string + jsonPath: + type: string + template: + type: string + type: object + icon: + description: Icon for overwriting default icon on the dashboard + type: string + kubeconfig: + description: Kubeconfig is the kubeconfig or the path to the kubeconfig file. + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + required: + - key + type: object + helmRef: + properties: + key: + description: Key is a JSONPath expression used to fetch the key from the merged JSON. + type: string + name: + type: string + required: + - key + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + required: + - key + type: object + serviceAccount: + description: ServiceAccount specifies the service account whose token should be fetched + type: string + type: object + type: object + labels: + additionalProperties: + type: string + description: Labels for the check + type: object + metrics: + description: Metrics to expose from check results + items: + properties: + labels: + items: + properties: + name: + type: string + value: + type: string + valueExpr: + type: string + required: + - name + type: object + type: array + name: + type: string + type: + type: string + value: + type: string + type: object + type: array + name: + description: Name of the check + type: string + namespace: + description: Namespace to insert the check into, if different to the namespace the canary is defined, e.g. + type: string + resources: + description: |- + Resources are kubernetes resources that are created & cleared + after every check run. + x-kubernetes-preserve-unknown-fields: true + staticResources: + description: |- + StaticResources are kubernetes resources that are created & only + cleared when the canary is deleted + x-kubernetes-preserve-unknown-fields: true + test: + properties: + expr: + type: string + javascript: + type: string + jsonPath: + type: string + template: + type: string + type: object + testResults: + items: + properties: + path: + type: string + podSelector: + type: string + type: + type: string + type: object + type: array + timeout: + type: string + transform: + properties: + expr: + type: string + javascript: + type: string + jsonPath: + type: string + template: + type: string + type: object + transformDeleteStrategy: + description: Transformed checks have a delete strategy on deletion they can either be marked healthy, unhealthy or left as is + type: string + waitForReady: + type: boolean + required: + - name + - resources + type: object + type: array ldap: items: properties: diff --git a/config/deploy/manifests.yaml b/config/deploy/manifests.yaml index 27deae355..1e7da5cec 100644 --- a/config/deploy/manifests.yaml +++ b/config/deploy/manifests.yaml @@ -4979,6 +4979,159 @@ spec: - name type: object type: array + kubernetesResource: + items: + properties: + description: + description: Description for the check + type: string + display: + properties: + expr: + type: string + javascript: + type: string + jsonPath: + type: string + template: + type: string + type: object + icon: + description: Icon for overwriting default icon on the dashboard + type: string + kubeconfig: + description: Kubeconfig is the kubeconfig or the path to the kubeconfig file. + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + required: + - key + type: object + helmRef: + properties: + key: + description: Key is a JSONPath expression used to fetch the key from the merged JSON. + type: string + name: + type: string + required: + - key + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + required: + - key + type: object + serviceAccount: + description: ServiceAccount specifies the service account whose token should be fetched + type: string + type: object + type: object + labels: + additionalProperties: + type: string + description: Labels for the check + type: object + metrics: + description: Metrics to expose from check results + items: + properties: + labels: + items: + properties: + name: + type: string + value: + type: string + valueExpr: + type: string + required: + - name + type: object + type: array + name: + type: string + type: + type: string + value: + type: string + type: object + type: array + name: + description: Name of the check + type: string + namespace: + description: Namespace to insert the check into, if different to the namespace the canary is defined, e.g. + type: string + resources: + description: |- + Resources are kubernetes resources that are created & cleared + after every check run. + x-kubernetes-preserve-unknown-fields: true + staticResources: + description: |- + StaticResources are kubernetes resources that are created & only + cleared when the canary is deleted + x-kubernetes-preserve-unknown-fields: true + test: + properties: + expr: + type: string + javascript: + type: string + jsonPath: + type: string + template: + type: string + type: object + testResults: + items: + properties: + path: + type: string + podSelector: + type: string + type: + type: string + type: object + type: array + timeout: + type: string + transform: + properties: + expr: + type: string + javascript: + type: string + jsonPath: + type: string + template: + type: string + type: object + transformDeleteStrategy: + description: Transformed checks have a delete strategy on deletion they can either be marked healthy, unhealthy or left as is + type: string + waitForReady: + type: boolean + required: + - name + - resources + type: object + type: array ldap: items: properties: diff --git a/config/schemas/canary.schema.json b/config/schemas/canary.schema.json index 4cb4001fd..ed8627643 100644 --- a/config/schemas/canary.schema.json +++ b/config/schemas/canary.schema.json @@ -585,6 +585,12 @@ }, "type": "array" }, + "kubernetesResource": { + "items": { + "$ref": "#/$defs/KubernetesResourceCheck" + }, + "type": "array" + }, "folder": { "items": { "$ref": "#/$defs/FolderCheck" @@ -2261,6 +2267,91 @@ "kind" ] }, + "KubernetesGenericCheckTestResultSelector": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "podSelector": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "KubernetesResourceCheck": { + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "labels": { + "$ref": "#/$defs/Labels" + }, + "transformDeleteStrategy": { + "type": "string" + }, + "metrics": { + "items": { + "$ref": "#/$defs/Metrics" + }, + "type": "array" + }, + "test": { + "$ref": "#/$defs/Template" + }, + "display": { + "$ref": "#/$defs/Template" + }, + "transform": { + "$ref": "#/$defs/Template" + }, + "staticResources": { + "items": { + "$ref": "#/$defs/Unstructured" + }, + "type": "array" + }, + "resources": { + "items": { + "$ref": "#/$defs/Unstructured" + }, + "type": "array" + }, + "kubeconfig": { + "$ref": "#/$defs/EnvVar" + }, + "waitForReady": { + "type": "boolean" + }, + "timeout": { + "type": "string" + }, + "testResults": { + "items": { + "$ref": "#/$defs/KubernetesGenericCheckTestResultSelector" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "resources" + ] + }, "LDAPCheck": { "properties": { "description": { @@ -3471,6 +3562,18 @@ "additionalProperties": false, "type": "object" }, + "Unstructured": { + "properties": { + "Object": { + "type": "object" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "Object" + ] + }, "VarSource": { "properties": { "fieldRef": { diff --git a/config/schemas/component.schema.json b/config/schemas/component.schema.json index 7a2801fdb..ec8199359 100644 --- a/config/schemas/component.schema.json +++ b/config/schemas/component.schema.json @@ -564,6 +564,12 @@ }, "type": "array" }, + "kubernetesResource": { + "items": { + "$ref": "#/$defs/KubernetesResourceCheck" + }, + "type": "array" + }, "folder": { "items": { "$ref": "#/$defs/FolderCheck" @@ -2515,6 +2521,91 @@ "kind" ] }, + "KubernetesGenericCheckTestResultSelector": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "podSelector": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "KubernetesResourceCheck": { + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "labels": { + "$ref": "#/$defs/Labels" + }, + "transformDeleteStrategy": { + "type": "string" + }, + "metrics": { + "items": { + "$ref": "#/$defs/Metrics" + }, + "type": "array" + }, + "test": { + "$ref": "#/$defs/Template" + }, + "display": { + "$ref": "#/$defs/Template" + }, + "transform": { + "$ref": "#/$defs/Template" + }, + "staticResources": { + "items": { + "$ref": "#/$defs/Unstructured" + }, + "type": "array" + }, + "resources": { + "items": { + "$ref": "#/$defs/Unstructured" + }, + "type": "array" + }, + "kubeconfig": { + "$ref": "#/$defs/EnvVar" + }, + "waitForReady": { + "type": "boolean" + }, + "timeout": { + "type": "string" + }, + "testResults": { + "items": { + "$ref": "#/$defs/KubernetesGenericCheckTestResultSelector" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "resources" + ] + }, "LDAPCheck": { "properties": { "description": { @@ -3926,6 +4017,18 @@ "additionalProperties": false, "type": "object" }, + "Unstructured": { + "properties": { + "Object": { + "type": "object" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "Object" + ] + }, "VarSource": { "properties": { "fieldRef": { diff --git a/config/schemas/topology.schema.json b/config/schemas/topology.schema.json index f1d5af72c..ef82e6863 100644 --- a/config/schemas/topology.schema.json +++ b/config/schemas/topology.schema.json @@ -564,6 +564,12 @@ }, "type": "array" }, + "kubernetesResource": { + "items": { + "$ref": "#/$defs/KubernetesResourceCheck" + }, + "type": "array" + }, "folder": { "items": { "$ref": "#/$defs/FolderCheck" @@ -2485,6 +2491,91 @@ "kind" ] }, + "KubernetesGenericCheckTestResultSelector": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "podSelector": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "KubernetesResourceCheck": { + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "labels": { + "$ref": "#/$defs/Labels" + }, + "transformDeleteStrategy": { + "type": "string" + }, + "metrics": { + "items": { + "$ref": "#/$defs/Metrics" + }, + "type": "array" + }, + "test": { + "$ref": "#/$defs/Template" + }, + "display": { + "$ref": "#/$defs/Template" + }, + "transform": { + "$ref": "#/$defs/Template" + }, + "staticResources": { + "items": { + "$ref": "#/$defs/Unstructured" + }, + "type": "array" + }, + "resources": { + "items": { + "$ref": "#/$defs/Unstructured" + }, + "type": "array" + }, + "kubeconfig": { + "$ref": "#/$defs/EnvVar" + }, + "waitForReady": { + "type": "boolean" + }, + "timeout": { + "type": "string" + }, + "testResults": { + "items": { + "$ref": "#/$defs/KubernetesGenericCheckTestResultSelector" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "resources" + ] + }, "LDAPCheck": { "properties": { "description": { @@ -3977,6 +4068,18 @@ "additionalProperties": false, "type": "object" }, + "Unstructured": { + "properties": { + "Object": { + "type": "object" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "Object" + ] + }, "VarSource": { "properties": { "fieldRef": { diff --git a/fixtures/k8s/ingress_accessibility.yaml b/fixtures/k8s/ingress_accessibility.yaml new file mode 100644 index 000000000..f09a7396f --- /dev/null +++ b/fixtures/k8s/ingress_accessibility.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: canaries.flanksource.com/v1 +kind: Canary +metadata: + name: ingress-tests + namespace: default +spec: + schedule: '@every 5m' + kubernetesResource: + - name: ingress-accessibility-check + description: "deploy a pod and check that it is accessible via ingress" + namespace: default + resources: + - apiVersion: v1 + kind: Pod + metadata: + name: nginx-pod + spec: + containers: + - name: nginx + image: nginx:latest + ports: + - containerPort: 80 + waitForReady: true + timeout: 10m + # checks: + # - http: + # url: "{{.staticResources[0].ingress.spec.hostname}}"