pkg/apis/common/v1/common.go (184 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.
package v1
import (
"errors"
"fmt"
"reflect"
v1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type DeploymentHealth string
const (
GreenHealth DeploymentHealth = "green"
RedHealth DeploymentHealth = "red"
)
// DeploymentStatus represents status information about a deployment.
type DeploymentStatus struct {
// Selector is the label selector used to find all pods.
Selector string `json:"selector,omitempty"`
// Count corresponds to Scale.Status.Replicas, which is the actual number of observed instances of the scaled object.
// +optional
Count int32 `json:"count"`
// AvailableNodes is the number of available replicas in the deployment.
AvailableNodes int32 `json:"availableNodes,omitempty"`
// Version of the stack resource currently running. During version upgrades, multiple versions may run
// in parallel: this value specifies the lowest version currently running.
Version string `json:"version,omitempty"`
// Health of the deployment.
Health DeploymentHealth `json:"health,omitempty"`
}
// IsDegraded returns true if the current status is worse than the previous.
func (ds DeploymentStatus) IsDegraded(prev DeploymentStatus) bool {
return prev.Health == GreenHealth && ds.Health != GreenHealth
}
// ConfigMapRef is a reference to a config map that exists in the same namespace as the referring resource.
type ConfigMapRef struct {
ConfigMapName string `json:"configMapName,omitempty"`
}
func (c ConfigMapRef) IsDefined() bool {
return len(c.ConfigMapName) > 0
}
// SecretRef is a reference to a secret that exists in the same namespace.
type SecretRef struct {
// SecretName is the name of the secret.
SecretName string `json:"secretName,omitempty"`
}
// LocalObjectSelector defines a reference to a Kubernetes object corresponding to an Elastic resource managed by the operator
type LocalObjectSelector struct {
// Namespace of the Kubernetes object. If empty, defaults to the current namespace.
Namespace string `json:"namespace,omitempty"`
// Name of an existing Kubernetes object corresponding to an Elastic resource managed by ECK.
Name string `json:"name,omitempty"`
// ServiceName is the name of an existing Kubernetes service which is used to make requests to the referenced
// object. It has to be in the same namespace as the referenced resource. If left empty, the default HTTP service of
// the referenced resource is used.
ServiceName string `json:"serviceName,omitempty"`
}
// WithDefaultNamespace adds a default namespace to a given LocalObjectSelector if none is set.
func (o LocalObjectSelector) WithDefaultNamespace(defaultNamespace string) LocalObjectSelector {
if len(o.Namespace) > 0 {
return o
}
return LocalObjectSelector{
Namespace: defaultNamespace,
Name: o.Name,
ServiceName: o.ServiceName,
}
}
// NamespacedName is a convenience method to turn an LocalObjectSelector into a NamespacedName.
func (o LocalObjectSelector) NamespacedName() types.NamespacedName {
return types.NamespacedName{
Name: o.Name,
Namespace: o.Namespace,
}
}
// IsDefined checks if the local object selector is not nil and has a name.
// Namespace is not mandatory as it may be inherited by the parent object.
func (o *LocalObjectSelector) IsDefined() bool {
return o != nil && o.Name != ""
}
// ObjectSelector defines a reference to a Kubernetes object which can be an Elastic resource managed by the operator
// or a Secret describing an external Elastic resource not managed by the operator.
type ObjectSelector struct {
// Namespace of the Kubernetes object. If empty, defaults to the current namespace.
Namespace string `json:"namespace,omitempty"`
// Name of an existing Kubernetes object corresponding to an Elastic resource managed by ECK.
Name string `json:"name,omitempty"`
// ServiceName is the name of an existing Kubernetes service which is used to make requests to the referenced
// object. It has to be in the same namespace as the referenced resource. If left empty, the default HTTP service of
// the referenced resource is used.
ServiceName string `json:"serviceName,omitempty"`
// SecretName is the name of an existing Kubernetes secret that contains connection information for associating an
// Elastic resource not managed by the operator. The referenced secret must contain the following:
// - `url`: the URL to reach the Elastic resource
// - `username`: the username of the user to be authenticated to the Elastic resource
// - `password`: the password of the user to be authenticated to the Elastic resource
// - `ca.crt`: the CA certificate in PEM format (optional)
// - `api-key`: the key to authenticate against the Elastic resource instead of a username and password (supported only for `elasticsearchRefs` in AgentSpec and in BeatSpec)
// This field cannot be used in combination with the other fields name, namespace or serviceName.
SecretName string `json:"secretName,omitempty"`
}
// WithDefaultNamespace adds a default namespace to a given ObjectSelector if none is set.
func (o ObjectSelector) WithDefaultNamespace(defaultNamespace string) ObjectSelector {
if len(o.Namespace) > 0 {
return o
}
return ObjectSelector{
Namespace: defaultNamespace,
Name: o.Name,
ServiceName: o.ServiceName,
SecretName: o.SecretName,
}
}
// NameOrSecretName returns the name or the secret name of the ObjectSelector.
// Name or secret name are mutually exclusive. Validation rules ensure that exactly one of the two is set.
func (o ObjectSelector) NameOrSecretName() string {
if o.SecretName != "" {
return o.SecretName
}
return o.Name
}
// NamespacedName is a convenience method to turn an ObjectSelector into a NamespacedName.
func (o ObjectSelector) NamespacedName() types.NamespacedName {
return types.NamespacedName{
Name: o.NameOrSecretName(),
Namespace: o.Namespace,
}
}
// IsDefined checks if the object selector is not nil and has a name or a secret name.
// Namespace is not mandatory as it may be inherited by the parent object.
func (o *ObjectSelector) IsDefined() bool {
return o != nil && o.NameOrSecretName() != ""
}
// IsExternal returns true when the object selector references a Kubernetes secret describing an external
// referenced object not managed by the operator.
func (o ObjectSelector) IsExternal() bool {
return o.IsDefined() && o.SecretName != ""
}
func (o ObjectSelector) IsValid() error {
if o.Name != "" && o.SecretName != "" {
return errors.New("specify name or secretName, not both")
}
if o.SecretName != "" && (o.ServiceName != "" || o.Namespace != "") {
return errors.New("serviceName or namespace can only be used in combination with name, not with secretName")
}
if o.Name == "" && (o.ServiceName != "") {
return errors.New("serviceName can only be used in combination with name")
}
if o.Name == "" && (o.Namespace != "") {
return errors.New("namespace can only be used in combination with name")
}
return nil
}
// ToID returns a string representing the object selector that can be used as a unique identifier.
func (o ObjectSelector) ToID() string {
if o.Namespace != "" {
return fmt.Sprintf("%s-%s", o.Namespace, o.NameOrSecretName())
}
return o.NameOrSecretName()
}
// HTTPConfig holds the HTTP layer configuration for resources.
type HTTPConfig struct {
// Service defines the template for the associated Kubernetes Service object.
Service ServiceTemplate `json:"service,omitempty"`
// TLS defines options for configuring TLS for HTTP.
TLS TLSOptions `json:"tls,omitempty"`
}
// Protocol returns the inferrred protocol (http or https) for this configuration.
func (http HTTPConfig) Protocol() string {
if http.TLS.Enabled() {
return "https"
}
return "http"
}
// TLSOptions holds TLS configuration options.
type TLSOptions struct {
// SelfSignedCertificate allows configuring the self-signed certificate generated by the operator.
SelfSignedCertificate *SelfSignedCertificate `json:"selfSignedCertificate,omitempty"`
// Certificate is a reference to a Kubernetes secret that contains the certificate and private key for enabling TLS.
// The referenced secret should contain the following:
//
// - `ca.crt`: The certificate authority (optional).
// - `tls.crt`: The certificate (or a chain).
// - `tls.key`: The private key to the first certificate in the certificate chain.
Certificate SecretRef `json:"certificate,omitempty"`
}
// Enabled returns true when TLS is enabled based on this option struct.
func (tls TLSOptions) Enabled() bool {
selfSigned := tls.SelfSignedCertificate
return selfSigned == nil || !selfSigned.Disabled || tls.Certificate.SecretName != ""
}
// SelfSignedCertificate holds configuration for the self-signed certificate generated by the operator.
type SelfSignedCertificate struct {
// SubjectAlternativeNames is a list of SANs to include in the generated HTTP TLS certificate.
SubjectAlternativeNames []SubjectAlternativeName `json:"subjectAltNames,omitempty"`
// Disabled indicates that the provisioning of the self-signed certifcate should be disabled.
Disabled bool `json:"disabled,omitempty"`
}
// SubjectAlternativeName represents a SAN entry in a x509 certificate.
type SubjectAlternativeName struct {
// DNS is the DNS name of the subject.
DNS string `json:"dns,omitempty"`
// IP is the IP address of the subject.
IP string `json:"ip,omitempty"`
}
// ServiceTemplate defines the template for a Kubernetes Service.
type ServiceTemplate struct {
// ObjectMeta is the metadata of the service.
// The name and namespace provided here are managed by ECK and will be ignored.
// +kubebuilder:validation:Optional
ObjectMeta metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec is the specification of the service.
// +kubebuilder:validation:Optional
Spec v1.ServiceSpec `json:"spec,omitempty"`
}
// DefaultPodDisruptionBudgetMaxUnavailable is the default max unavailable pods in a PDB.
var DefaultPodDisruptionBudgetMaxUnavailable = intstr.FromInt(1)
// PodDisruptionBudgetTemplate defines the template for creating a PodDisruptionBudget.
type PodDisruptionBudgetTemplate struct {
// ObjectMeta is the metadata of the PDB.
// The name and namespace provided here are managed by ECK and will be ignored.
// +kubebuilder:validation:Optional
ObjectMeta metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec is the specification of the PDB.
// +kubebuilder:validation:Optional
Spec policyv1.PodDisruptionBudgetSpec `json:"spec,omitempty"`
}
// IsDisabled returns true if the PodDisruptionBudget is explicitly disabled (not nil, but empty).
func (p *PodDisruptionBudgetTemplate) IsDisabled() bool {
return reflect.DeepEqual(p, &PodDisruptionBudgetTemplate{})
}
// NamespacedSecretSource defines a data source based on a Kubernetes Secret in a given namespace.
type NamespacedSecretSource struct {
// Namespace is the namespace of the secret.
Namespace string `json:"namespace"`
// SecretName is the name of the secret.
SecretName string `json:"secretName"`
// Entries define how to project each key-value pair in the secret to filesystem paths.
// If not defined, all keys will be projected to similarly named paths in the filesystem.
// If defined, only the specified keys will be projected to the corresponding paths.
// +kubebuilder:validation:Optional
Entries []KeyToPath `json:"entries,omitempty"`
}
// SecretSource defines a data source based on a Kubernetes Secret.
type SecretSource struct {
// SecretName is the name of the secret.
SecretName string `json:"secretName"`
// Entries define how to project each key-value pair in the secret to filesystem paths.
// If not defined, all keys will be projected to similarly named paths in the filesystem.
// If defined, only the specified keys will be projected to the corresponding paths.
// +kubebuilder:validation:Optional
Entries []KeyToPath `json:"entries,omitempty"`
}
// KeyToPath defines how to map a key in a Secret object to a filesystem path.
type KeyToPath struct {
// Key is the key contained in the secret.
Key string `json:"key"`
// Path is the relative file path to map the key to.
// Path must not be an absolute file path and must not contain any ".." components.
// +kubebuilder:validation:Optional
Path string `json:"path,omitempty"`
}
// ConfigSource references configuration settings.
type ConfigSource struct {
// SecretName references a Kubernetes Secret in the same namespace as the resource that will consume it.
//
// Examples:
// ---
// # Filebeat configuration
// kind: Secret
// apiVersion: v1
// metadata:
// name: filebeat-user-config
// stringData:
// beat.yml: |-
// filebeat.inputs:
// - type: filestream
// paths:
// - /var/log/containers/*.log
// parsers:
// - container: ~
// prospector:
// scanner:
// fingerprint.enabled: true
// symlinks: true
// file_identity.fingerprint: ~
// processors:
// - add_kubernetes_metadata:
// node: ${NODE_NAME}
// matchers:
// - logs_path:
// logs_path: "/var/log/containers/"
// processors:
// - add_cloud_metadata: {}
// - add_host_metadata: {}
// ---
// # EnterpriseSearch configuration
// kind: Secret
// apiVersion: v1
// metadata:
// name: smtp-credentials
// stringData:
// enterprise-search.yml: |-
// email.account.enabled: true
// email.account.smtp.auth: plain
// email.account.smtp.starttls.enable: false
// email.account.smtp.host: 127.0.0.1
// email.account.smtp.port: 25
// email.account.smtp.user: myuser
// email.account.smtp.password: mypassword
// email.account.email_defaults.from: my@email.com
// ---
SecretRef `json:",inline"`
}
// HasObservedGeneration allows a return of any object's observed generation.
// +kubebuilder:object:generate=false
type HasObservedGeneration interface {
client.Object
GetObservedGeneration() int64
}
// TypeLabelName is used to represent a resource type in k8s resources
const TypeLabelName = "common.k8s.elastic.co/type"
// HasIdentityLabels allows a return of Elastic assigned labels for any object.
// +kubebuilder:object:generate=false
type HasIdentityLabels interface {
client.Object
GetIdentityLabels() map[string]string
}
// DisableDowngradeValidationAnnotation allows circumventing downgrade/upgrade checks.
const DisableDowngradeValidationAnnotation = "eck.k8s.elastic.co/disable-downgrade-validation"
// IsConfiguredToAllowDowngrades returns true if the DisableDowngradeValidation annotation is set to the value of true.
func IsConfiguredToAllowDowngrades(o metav1.Object) bool {
val, exists := o.GetAnnotations()[DisableDowngradeValidationAnnotation]
return exists && val == "true"
}