From faa681ffc0fdac63190c19da461a2e96ec1defa9 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Fri, 20 Jan 2023 17:26:31 -0500 Subject: [PATCH 01/21] Add ClusterCryostat type and controller --- api/v1beta1/cluster_cryostat_types.go | 91 + api/v1beta1/zz_generated.deepcopy.go | 96 + ...operator.cryostat.io_clustercryostats.yaml | 4510 +++++++++++++++++ .../bases/operator.cryostat.io_cryostats.yaml | 4478 ++++++++++++++++ internal/constants/constants.go | 56 + internal/controllers/certmanager.go | 44 +- .../cluster_cryostat_controller.go | 142 + internal/controllers/common/common_utils.go | 7 +- .../resource_definitions/certificates.go | 50 +- .../resource_definitions.go | 83 +- internal/controllers/common/tls.go | 6 +- internal/controllers/ingresses.go | 18 +- internal/controllers/model/instance.go | 79 + internal/controllers/openshift.go | 28 +- internal/controllers/pvc.go | 13 +- internal/controllers/rbac.go | 48 +- internal/controllers/reconciler.go | 202 +- internal/controllers/reconciler_test.go | 5 +- internal/controllers/routes.go | 30 +- internal/controllers/secrets.go | 29 +- internal/controllers/services.go | 55 +- internal/main.go | 4 +- 22 files changed, 9723 insertions(+), 351 deletions(-) create mode 100644 api/v1beta1/cluster_cryostat_types.go create mode 100644 config/crd/bases/operator.cryostat.io_clustercryostats.yaml create mode 100644 internal/constants/constants.go create mode 100644 internal/controllers/cluster_cryostat_controller.go create mode 100644 internal/controllers/model/instance.go diff --git a/api/v1beta1/cluster_cryostat_types.go b/api/v1beta1/cluster_cryostat_types.go new file mode 100644 index 000000000..23037bb88 --- /dev/null +++ b/api/v1beta1/cluster_cryostat_types.go @@ -0,0 +1,91 @@ +// Copyright The Cryostat Authors +// +// The Universal Permissive License (UPL), Version 1.0 +// +// Subject to the condition set forth below, permission is hereby granted to any +// person obtaining a copy of this software, associated documentation and/or data +// (collectively the "Software"), free of charge and under any and all copyright +// rights in the Software, and any and all patent rights owned or freely +// licensable by each licensor hereunder covering either (i) the unmodified +// Software as contributed to or provided by such licensor, or (ii) the Larger +// Works (as defined below), to deal in both +// +// (a) the Software, and +// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +// one is included with the Software (each a "Larger Work" to which the Software +// is contributed by such licensors), +// +// without restriction, including without limitation the rights to copy, create +// derivative works of, display, perform, and distribute the Software and make, +// use, sell, offer for sale, import, export, have made, and have sold the +// Software and the Larger Work(s), and to sublicense the foregoing rights on +// either these or other terms. +// +// This license is subject to the following condition: +// The above copyright notice and either this complete permission notice or at +// a minimum a reference to the UPL must be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ClusterCryostatSpec defines the desired state of ClusterCryostat. +type ClusterCryostatSpec struct { + // Namespace where Cryostat should be installed. + // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} + InstallNamespace string `json:"installNamespace"` + // List of namespaces whose workloads Cryostat should be + // permitted to access and profile. Defaults to `spec.installNamespace`. + // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} + TargetNamespaces []string `json:"targetNamespaces,omitempty"` + CryostatSpec `json:",inline"` +} + +// ClusterCryostatStatus defines the observed state of ClusterCryostat. +type ClusterCryostatStatus struct { + CryostatStatus `json:",inline"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:storageversion +// +kubebuilder:resource:path=clustercryostats,scope=Cluster + +// ClusterCryostat allows for the installation of a multi-namespace or cluster-wide +// Cryostat. It contains configuration options for controlling the Deployment of +// the Cryostat application and its related components. A Cryostat instance +// must be created to instruct the operator to deploy the Cryostat application. +// +operator-sdk:csv:customresourcedefinitions:resources={{Deployment,v1},{Ingress,v1},{PersistentVolumeClaim,v1},{Secret,v1},{Service,v1},{Route,v1},{ConsoleLink,v1}} +// +kubebuilder:printcolumn:name="Application URL",type=string,JSONPath=`.status.applicationUrl` +// +kubebuilder:printcolumn:name="Grafana Secret",type=string,JSONPath=`.status.grafanaSecret` +type ClusterCryostat struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ClusterCryostatSpec `json:"spec,omitempty"` + Status ClusterCryostatStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ClusterCryostatList contains a list of ClusterCryostat +type ClusterCryostatList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Cryostat `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ClusterCryostat{}, &ClusterCryostatList{}) +} diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 1ea8a7ad7..9238b4179 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -113,6 +113,102 @@ func (in *CertificateSecret) DeepCopy() *CertificateSecret { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterCryostat) DeepCopyInto(out *ClusterCryostat) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCryostat. +func (in *ClusterCryostat) DeepCopy() *ClusterCryostat { + if in == nil { + return nil + } + out := new(ClusterCryostat) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterCryostat) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterCryostatList) DeepCopyInto(out *ClusterCryostatList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Cryostat, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCryostatList. +func (in *ClusterCryostatList) DeepCopy() *ClusterCryostatList { + if in == nil { + return nil + } + out := new(ClusterCryostatList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterCryostatList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterCryostatSpec) DeepCopyInto(out *ClusterCryostatSpec) { + *out = *in + if in.TargetNamespaces != nil { + in, out := &in.TargetNamespaces, &out.TargetNamespaces + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.CryostatSpec.DeepCopyInto(&out.CryostatSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCryostatSpec. +func (in *ClusterCryostatSpec) DeepCopy() *ClusterCryostatSpec { + if in == nil { + return nil + } + out := new(ClusterCryostatSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterCryostatStatus) DeepCopyInto(out *ClusterCryostatStatus) { + *out = *in + in.CryostatStatus.DeepCopyInto(&out.CryostatStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCryostatStatus. +func (in *ClusterCryostatStatus) DeepCopy() *ClusterCryostatStatus { + if in == nil { + return nil + } + out := new(ClusterCryostatStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CoreServiceConfig) DeepCopyInto(out *CoreServiceConfig) { *out = *in diff --git a/config/crd/bases/operator.cryostat.io_clustercryostats.yaml b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml new file mode 100644 index 000000000..4f24613e3 --- /dev/null +++ b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml @@ -0,0 +1,4510 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: clustercryostats.operator.cryostat.io +spec: + group: operator.cryostat.io + names: + kind: ClusterCryostat + listKind: ClusterCryostatList + plural: clustercryostats + singular: clustercryostat + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.applicationUrl + name: Application URL + type: string + - jsonPath: .status.grafanaSecret + name: Grafana Secret + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: ClusterCryostat allows for the installation of a multi-namespace + or cluster-wide Cryostat. It contains configuration options for controlling + the Deployment of the Cryostat application and its related components. A + Cryostat instance must be created to instruct the operator to deploy the + Cryostat application. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ClusterCryostatSpec defines the desired state of ClusterCryostat. + properties: + authProperties: + description: Override default authorization properties for Cryostat + on OpenShift. + properties: + clusterRoleName: + description: 'Name of the ClusterRole to use when Cryostat requests + a role-scoped OAuth token. This ClusterRole should contain permissions + for all Kubernetes objects listed in custom permission mapping. + More details: https://docs.openshift.com/container-platform/4.11/authentication/tokens-scoping.html#scoping-tokens-role-scope_configuring-internal-oauth' + type: string + configMapName: + description: Name of config map in the local namespace. + type: string + filename: + description: Filename within config map containing the resource + mapping. + type: string + required: + - clusterRoleName + - configMapName + - filename + type: object + enableCertManager: + description: Use cert-manager to secure in-cluster communication between + Cryostat components. Requires cert-manager to be installed. + type: boolean + eventTemplates: + description: List of Flight Recorder Event Templates to preconfigure + in Cryostat. + items: + description: A ConfigMap containing a .jfc template file. + properties: + configMapName: + description: Name of config map in the local namespace. + type: string + filename: + description: Filename within config map containing the template + file. + type: string + required: + - configMapName + - filename + type: object + type: array + installNamespace: + description: Namespace where Cryostat should be installed. + type: string + jmxCacheOptions: + description: Options to customize the JMX target connections cache + for the Cryostat application. + properties: + targetCacheSize: + description: The maximum number of JMX connections to cache. Use + `-1` for an unlimited cache size (TTL expiration only). Defaults + to `-1`. + format: int32 + minimum: -1 + type: integer + targetCacheTTL: + description: The time to live (in seconds) for cached JMX connections. + Defaults to `10`. + format: int32 + minimum: 1 + type: integer + type: object + jmxCredentialsDatabaseOptions: + description: Options to configure the Cryostat application's JMX credentials + database. + properties: + databaseSecretName: + description: Name of the secret containing the password to encrypt + JMX credentials database. + type: string + type: object + maxWsConnections: + description: The maximum number of WebSocket client connections allowed + (minimum 1, default unlimited). + format: int32 + minimum: 1 + type: integer + minimal: + description: Deploy a pared-down Cryostat instance with no Grafana + Dashboard or JFR Data Source. + type: boolean + networkOptions: + description: Options to control how the operator exposes the application + outside of the cluster, such as using an Ingress or Route. + properties: + commandConfig: + description: "Specifications for how to expose the Cryostat command + service, which serves the WebSocket command channel. \n Deprecated: + CommandConfig is no longer used." + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Ingress or Route during + its creation. + type: object + ingressSpec: + description: Configuration for an Ingress object. Currently + subpaths are not supported, so unique hosts must be specified + (if a single external IP is being used) to differentiate + between ingresses/services. + properties: + defaultBackend: + description: DefaultBackend is the backend that should + handle requests that don't match any rule. If Rules + are not specified, DefaultBackend must be specified. + If DefaultBackend is not set, the handling of requests + that do not match any of the rules will be up to the + Ingress controller. + properties: + resource: + description: Resource is an ObjectRef to another Kubernetes + resource in the namespace of the Ingress object. + If resource is specified, a service.Name and service.Port + must not be specified. This is a mutually exclusive + setting with "Service". + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is + required. + type: string + kind: + description: Kind is the type of resource being + referenced + type: string + name: + description: Name is the name of resource being + referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a Service as a Backend. + This is a mutually exclusive setting with "Resource". + properties: + name: + description: Name is the referenced service. The + service must exist in the same namespace as + the Ingress object. + type: string + port: + description: Port of the referenced service. A + port name or port number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name of the port + on the Service. This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the numerical port + number (e.g. 80) on the Service. This is + a mutually exclusive setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + ingressClassName: + description: IngressClassName is the name of the IngressClass + cluster resource. The associated IngressClass defines + which controller will implement the resource. This replaces + the deprecated `kubernetes.io/ingress.class` annotation. + For backwards compatibility, when that annotation is + set, it must be given precedence over this field. The + controller may emit a warning if the field and annotation + have different values. Implementations of this API should + ignore Ingresses without a class specified. An IngressClass + resource may be marked as default, which can be used + to set a default value for this field. For more information, + refer to the IngressClass documentation. + type: string + rules: + description: A list of host rules used to configure the + Ingress. If unspecified, or no rule matches, all traffic + is sent to the default backend. + items: + description: IngressRule represents the rules mapping + the paths under a specified host to the related backend + services. Incoming requests are first evaluated for + a host match, then routed to the backend associated + with the matching IngressRuleValue. + properties: + host: + description: "Host is the fully qualified domain + name of a network host, as defined by RFC 3986. + Note the following deviations from the \"host\" + part of the URI as defined in RFC 3986: 1. IPs + are not allowed. Currently an IngressRuleValue + can only apply to the IP in the Spec of the parent + Ingress. 2. The `:` delimiter is not respected + because ports are not allowed. Currently the port + of an Ingress is implicitly :80 for http and :443 + for https. Both these may change in the future. + Incoming requests are matched against the host + before the IngressRuleValue. If the host is unspecified, + the Ingress routes all traffic based on the specified + IngressRuleValue. \n Host can be \"precise\" which + is a domain name without the terminating dot of + a network host (e.g. \"foo.bar.com\") or \"wildcard\", + which is a domain name prefixed with a single + wildcard label (e.g. \"*.foo.com\"). The wildcard + character '*' must appear by itself as the first + DNS label and matches only a single label. You + cannot have a wildcard label by itself (e.g. Host + == \"*\"). Requests will be matched against the + Host field in the following way: 1. If Host is + precise, the request matches this rule if the + http host header is equal to Host. 2. If Host + is a wildcard, then the request matches this rule + if the http host header is to equal to the suffix + (removing the first label) of the wildcard rule." + type: string + http: + description: 'HTTPIngressRuleValue is a list of + http selectors pointing to backends. In the example: + http:///? -> backend where + where parts of the url correspond to RFC 3986, + this resource will be used to match against everything + after the last ''/'' and before the first ''?'' + or ''#''.' + properties: + paths: + description: A collection of paths that map + requests to backends. + items: + description: HTTPIngressPath associates a + path with a backend. Incoming urls matching + the path are forwarded to the backend. + properties: + backend: + description: Backend defines the referenced + service endpoint to which the traffic + will be forwarded to. + properties: + resource: + description: Resource is an ObjectRef + to another Kubernetes resource in + the namespace of the Ingress object. + If resource is specified, a service.Name + and service.Port must not be specified. + This is a mutually exclusive setting + with "Service". + properties: + apiGroup: + description: APIGroup is the group + for the resource being referenced. + If APIGroup is not specified, + the specified Kind must be in + the core API group. For any + other third-party types, APIGroup + is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a + Service as a Backend. This is a + mutually exclusive setting with + "Resource". + properties: + name: + description: Name is the referenced + service. The service must exist + in the same namespace as the + Ingress object. + type: string + port: + description: Port of the referenced + service. A port name or port + number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name + of the port on the Service. + This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the + numerical port number (e.g. + 80) on the Service. This + is a mutually exclusive + setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + path: + description: Path is matched against the + path of an incoming request. Currently + it can contain characters disallowed + from the conventional "path" part of + a URL as defined by RFC 3986. Paths + must begin with a '/' and must be present + when using PathType with value "Exact" + or "Prefix". + type: string + pathType: + description: 'PathType determines the + interpretation of the Path matching. + PathType can be one of the following + values: * Exact: Matches the URL path + exactly. * Prefix: Matches based on + a URL path prefix split by ''/''. Matching + is done on a path element by element + basis. A path element refers is the + list of labels in the path split by + the ''/'' separator. A request is a + match for path p if every p is an element-wise + prefix of p of the request path. Note + that if the last element of the path + is a substring of the last element in + request path, it is not a match (e.g. + /foo/bar matches /foo/bar/baz, but does + not match /foo/barbaz). * ImplementationSpecific: + Interpretation of the Path matching + is up to the IngressClass. Implementations + can treat this as a separate PathType + or treat it identically to Prefix or + Exact path types. Implementations are + required to support all path types.' + type: string + required: + - backend + - pathType + type: object + type: array + x-kubernetes-list-type: atomic + required: + - paths + type: object + type: object + type: array + x-kubernetes-list-type: atomic + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Ingress or Route during + its creation. The label with key "app" is reserved for use + by the operator. + type: object + type: object + coreConfig: + description: Specifications for how to expose the Cryostat service, + which serves the Cryostat application. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Ingress or Route during + its creation. + type: object + ingressSpec: + description: Configuration for an Ingress object. Currently + subpaths are not supported, so unique hosts must be specified + (if a single external IP is being used) to differentiate + between ingresses/services. + properties: + defaultBackend: + description: DefaultBackend is the backend that should + handle requests that don't match any rule. If Rules + are not specified, DefaultBackend must be specified. + If DefaultBackend is not set, the handling of requests + that do not match any of the rules will be up to the + Ingress controller. + properties: + resource: + description: Resource is an ObjectRef to another Kubernetes + resource in the namespace of the Ingress object. + If resource is specified, a service.Name and service.Port + must not be specified. This is a mutually exclusive + setting with "Service". + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is + required. + type: string + kind: + description: Kind is the type of resource being + referenced + type: string + name: + description: Name is the name of resource being + referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a Service as a Backend. + This is a mutually exclusive setting with "Resource". + properties: + name: + description: Name is the referenced service. The + service must exist in the same namespace as + the Ingress object. + type: string + port: + description: Port of the referenced service. A + port name or port number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name of the port + on the Service. This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the numerical port + number (e.g. 80) on the Service. This is + a mutually exclusive setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + ingressClassName: + description: IngressClassName is the name of the IngressClass + cluster resource. The associated IngressClass defines + which controller will implement the resource. This replaces + the deprecated `kubernetes.io/ingress.class` annotation. + For backwards compatibility, when that annotation is + set, it must be given precedence over this field. The + controller may emit a warning if the field and annotation + have different values. Implementations of this API should + ignore Ingresses without a class specified. An IngressClass + resource may be marked as default, which can be used + to set a default value for this field. For more information, + refer to the IngressClass documentation. + type: string + rules: + description: A list of host rules used to configure the + Ingress. If unspecified, or no rule matches, all traffic + is sent to the default backend. + items: + description: IngressRule represents the rules mapping + the paths under a specified host to the related backend + services. Incoming requests are first evaluated for + a host match, then routed to the backend associated + with the matching IngressRuleValue. + properties: + host: + description: "Host is the fully qualified domain + name of a network host, as defined by RFC 3986. + Note the following deviations from the \"host\" + part of the URI as defined in RFC 3986: 1. IPs + are not allowed. Currently an IngressRuleValue + can only apply to the IP in the Spec of the parent + Ingress. 2. The `:` delimiter is not respected + because ports are not allowed. Currently the port + of an Ingress is implicitly :80 for http and :443 + for https. Both these may change in the future. + Incoming requests are matched against the host + before the IngressRuleValue. If the host is unspecified, + the Ingress routes all traffic based on the specified + IngressRuleValue. \n Host can be \"precise\" which + is a domain name without the terminating dot of + a network host (e.g. \"foo.bar.com\") or \"wildcard\", + which is a domain name prefixed with a single + wildcard label (e.g. \"*.foo.com\"). The wildcard + character '*' must appear by itself as the first + DNS label and matches only a single label. You + cannot have a wildcard label by itself (e.g. Host + == \"*\"). Requests will be matched against the + Host field in the following way: 1. If Host is + precise, the request matches this rule if the + http host header is equal to Host. 2. If Host + is a wildcard, then the request matches this rule + if the http host header is to equal to the suffix + (removing the first label) of the wildcard rule." + type: string + http: + description: 'HTTPIngressRuleValue is a list of + http selectors pointing to backends. In the example: + http:///? -> backend where + where parts of the url correspond to RFC 3986, + this resource will be used to match against everything + after the last ''/'' and before the first ''?'' + or ''#''.' + properties: + paths: + description: A collection of paths that map + requests to backends. + items: + description: HTTPIngressPath associates a + path with a backend. Incoming urls matching + the path are forwarded to the backend. + properties: + backend: + description: Backend defines the referenced + service endpoint to which the traffic + will be forwarded to. + properties: + resource: + description: Resource is an ObjectRef + to another Kubernetes resource in + the namespace of the Ingress object. + If resource is specified, a service.Name + and service.Port must not be specified. + This is a mutually exclusive setting + with "Service". + properties: + apiGroup: + description: APIGroup is the group + for the resource being referenced. + If APIGroup is not specified, + the specified Kind must be in + the core API group. For any + other third-party types, APIGroup + is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a + Service as a Backend. This is a + mutually exclusive setting with + "Resource". + properties: + name: + description: Name is the referenced + service. The service must exist + in the same namespace as the + Ingress object. + type: string + port: + description: Port of the referenced + service. A port name or port + number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name + of the port on the Service. + This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the + numerical port number (e.g. + 80) on the Service. This + is a mutually exclusive + setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + path: + description: Path is matched against the + path of an incoming request. Currently + it can contain characters disallowed + from the conventional "path" part of + a URL as defined by RFC 3986. Paths + must begin with a '/' and must be present + when using PathType with value "Exact" + or "Prefix". + type: string + pathType: + description: 'PathType determines the + interpretation of the Path matching. + PathType can be one of the following + values: * Exact: Matches the URL path + exactly. * Prefix: Matches based on + a URL path prefix split by ''/''. Matching + is done on a path element by element + basis. A path element refers is the + list of labels in the path split by + the ''/'' separator. A request is a + match for path p if every p is an element-wise + prefix of p of the request path. Note + that if the last element of the path + is a substring of the last element in + request path, it is not a match (e.g. + /foo/bar matches /foo/bar/baz, but does + not match /foo/barbaz). * ImplementationSpecific: + Interpretation of the Path matching + is up to the IngressClass. Implementations + can treat this as a separate PathType + or treat it identically to Prefix or + Exact path types. Implementations are + required to support all path types.' + type: string + required: + - backend + - pathType + type: object + type: array + x-kubernetes-list-type: atomic + required: + - paths + type: object + type: object + type: array + x-kubernetes-list-type: atomic + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Ingress or Route during + its creation. The label with key "app" is reserved for use + by the operator. + type: object + type: object + grafanaConfig: + description: Specifications for how to expose Cryostat's Grafana + service, which serves the Grafana dashboard. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Ingress or Route during + its creation. + type: object + ingressSpec: + description: Configuration for an Ingress object. Currently + subpaths are not supported, so unique hosts must be specified + (if a single external IP is being used) to differentiate + between ingresses/services. + properties: + defaultBackend: + description: DefaultBackend is the backend that should + handle requests that don't match any rule. If Rules + are not specified, DefaultBackend must be specified. + If DefaultBackend is not set, the handling of requests + that do not match any of the rules will be up to the + Ingress controller. + properties: + resource: + description: Resource is an ObjectRef to another Kubernetes + resource in the namespace of the Ingress object. + If resource is specified, a service.Name and service.Port + must not be specified. This is a mutually exclusive + setting with "Service". + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is + required. + type: string + kind: + description: Kind is the type of resource being + referenced + type: string + name: + description: Name is the name of resource being + referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a Service as a Backend. + This is a mutually exclusive setting with "Resource". + properties: + name: + description: Name is the referenced service. The + service must exist in the same namespace as + the Ingress object. + type: string + port: + description: Port of the referenced service. A + port name or port number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name of the port + on the Service. This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the numerical port + number (e.g. 80) on the Service. This is + a mutually exclusive setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + ingressClassName: + description: IngressClassName is the name of the IngressClass + cluster resource. The associated IngressClass defines + which controller will implement the resource. This replaces + the deprecated `kubernetes.io/ingress.class` annotation. + For backwards compatibility, when that annotation is + set, it must be given precedence over this field. The + controller may emit a warning if the field and annotation + have different values. Implementations of this API should + ignore Ingresses without a class specified. An IngressClass + resource may be marked as default, which can be used + to set a default value for this field. For more information, + refer to the IngressClass documentation. + type: string + rules: + description: A list of host rules used to configure the + Ingress. If unspecified, or no rule matches, all traffic + is sent to the default backend. + items: + description: IngressRule represents the rules mapping + the paths under a specified host to the related backend + services. Incoming requests are first evaluated for + a host match, then routed to the backend associated + with the matching IngressRuleValue. + properties: + host: + description: "Host is the fully qualified domain + name of a network host, as defined by RFC 3986. + Note the following deviations from the \"host\" + part of the URI as defined in RFC 3986: 1. IPs + are not allowed. Currently an IngressRuleValue + can only apply to the IP in the Spec of the parent + Ingress. 2. The `:` delimiter is not respected + because ports are not allowed. Currently the port + of an Ingress is implicitly :80 for http and :443 + for https. Both these may change in the future. + Incoming requests are matched against the host + before the IngressRuleValue. If the host is unspecified, + the Ingress routes all traffic based on the specified + IngressRuleValue. \n Host can be \"precise\" which + is a domain name without the terminating dot of + a network host (e.g. \"foo.bar.com\") or \"wildcard\", + which is a domain name prefixed with a single + wildcard label (e.g. \"*.foo.com\"). The wildcard + character '*' must appear by itself as the first + DNS label and matches only a single label. You + cannot have a wildcard label by itself (e.g. Host + == \"*\"). Requests will be matched against the + Host field in the following way: 1. If Host is + precise, the request matches this rule if the + http host header is equal to Host. 2. If Host + is a wildcard, then the request matches this rule + if the http host header is to equal to the suffix + (removing the first label) of the wildcard rule." + type: string + http: + description: 'HTTPIngressRuleValue is a list of + http selectors pointing to backends. In the example: + http:///? -> backend where + where parts of the url correspond to RFC 3986, + this resource will be used to match against everything + after the last ''/'' and before the first ''?'' + or ''#''.' + properties: + paths: + description: A collection of paths that map + requests to backends. + items: + description: HTTPIngressPath associates a + path with a backend. Incoming urls matching + the path are forwarded to the backend. + properties: + backend: + description: Backend defines the referenced + service endpoint to which the traffic + will be forwarded to. + properties: + resource: + description: Resource is an ObjectRef + to another Kubernetes resource in + the namespace of the Ingress object. + If resource is specified, a service.Name + and service.Port must not be specified. + This is a mutually exclusive setting + with "Service". + properties: + apiGroup: + description: APIGroup is the group + for the resource being referenced. + If APIGroup is not specified, + the specified Kind must be in + the core API group. For any + other third-party types, APIGroup + is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a + Service as a Backend. This is a + mutually exclusive setting with + "Resource". + properties: + name: + description: Name is the referenced + service. The service must exist + in the same namespace as the + Ingress object. + type: string + port: + description: Port of the referenced + service. A port name or port + number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name + of the port on the Service. + This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the + numerical port number (e.g. + 80) on the Service. This + is a mutually exclusive + setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + path: + description: Path is matched against the + path of an incoming request. Currently + it can contain characters disallowed + from the conventional "path" part of + a URL as defined by RFC 3986. Paths + must begin with a '/' and must be present + when using PathType with value "Exact" + or "Prefix". + type: string + pathType: + description: 'PathType determines the + interpretation of the Path matching. + PathType can be one of the following + values: * Exact: Matches the URL path + exactly. * Prefix: Matches based on + a URL path prefix split by ''/''. Matching + is done on a path element by element + basis. A path element refers is the + list of labels in the path split by + the ''/'' separator. A request is a + match for path p if every p is an element-wise + prefix of p of the request path. Note + that if the last element of the path + is a substring of the last element in + request path, it is not a match (e.g. + /foo/bar matches /foo/bar/baz, but does + not match /foo/barbaz). * ImplementationSpecific: + Interpretation of the Path matching + is up to the IngressClass. Implementations + can treat this as a separate PathType + or treat it identically to Prefix or + Exact path types. Implementations are + required to support all path types.' + type: string + required: + - backend + - pathType + type: object + type: array + x-kubernetes-list-type: atomic + required: + - paths + type: object + type: object + type: array + x-kubernetes-list-type: atomic + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Ingress or Route during + its creation. The label with key "app" is reserved for use + by the operator. + type: object + type: object + type: object + reportOptions: + description: Options to configure Cryostat Automated Report Analysis. + properties: + replicas: + description: The number of report sidecar replica containers to + deploy. Each replica can service one report generation request + at a time. + format: int32 + type: integer + resources: + description: The resources allocated to each sidecar replica. + A replica with more resources can handle larger input recordings + and will process them faster. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + schedulingOptions: + description: Options to configure scheduling for the reports deployment + properties: + affinity: + description: Affinity rules for scheduling Cryostat pods. + properties: + nodeAffinity: + description: 'Node affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. The + node that is most preferred is the one with the + greatest sum of weights, i.e. for each node that + meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements + of this field and adding "weight" to the sum if + the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term + matches all objects with implicit weight 0 (i.e. + it's a no-op). A null preferred scheduling term + matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching + the corresponding nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified + by this field are not met at scheduling time, the + pod will not be scheduled onto the node. If the + affinity requirements specified by this field cease + to be met at some point during pod execution (e.g. + due to an update), the system may or may not try + to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector + terms. The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them + are ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: 'Pod affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. The + node that is most preferred is the one with the + greatest sum of weights, i.e. for each node that + meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements + of this field and adding "weight" to the sum if + the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum + are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of + resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set + of namespaces that the term applies to. + The term is applied to the union of the + namespaces selected by this field and + the ones listed in the namespaces field. + null selector and null or empty namespaces + list means "this pod's namespace". An + empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static + list of namespace names that the term + applies to. The term is applied to the + union of the namespaces listed in this + field and the ones selected by namespaceSelector. + null or empty namespaces list and null + namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose + value of the label with key topologyKey + matches that of any node on which any + of the selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching + the corresponding podAffinityTerm, in the + range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified + by this field are not met at scheduling time, the + pod will not be scheduled onto the node. If the + affinity requirements specified by this field cease + to be met at some point during pod execution (e.g. + due to a pod label update), the system may or may + not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes + corresponding to each podAffinityTerm are intersected, + i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those + matching the labelSelector relative to the given + namespace(s)) that this pod should be co-located + (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node + whose value of the label with key + matches that of any node on which a pod of the + set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: 'Pod anti-affinity scheduling rules for a + Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. The + node that is most preferred is the one with the + greatest sum of weights, i.e. for each node that + meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity + expressions, etc.), compute a sum by iterating through + the elements of this field and adding "weight" to + the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum + are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of + resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set + of namespaces that the term applies to. + The term is applied to the union of the + namespaces selected by this field and + the ones listed in the namespaces field. + null selector and null or empty namespaces + list means "this pod's namespace". An + empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static + list of namespace names that the term + applies to. The term is applied to the + union of the namespaces listed in this + field and the ones selected by namespaceSelector. + null or empty namespaces list and null + namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose + value of the label with key topologyKey + matches that of any node on which any + of the selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching + the corresponding podAffinityTerm, in the + range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the + pod will not be scheduled onto the node. If the + anti-affinity requirements specified by this field + cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may + or may not try to eventually evict the pod from + its node. When there are multiple elements, the + lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those + matching the labelSelector relative to the given + namespace(s)) that this pod should be co-located + (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node + whose value of the label with key + matches that of any node on which a pod of the + set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: 'Label selector used to schedule a Cryostat pod + to a node. See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + tolerations: + description: 'Tolerations to allow scheduling of Cryostat + pods to tainted nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, + allowed values are NoSchedule, PreferNoSchedule and + NoExecute. + type: string + key: + description: Key is the taint key that the toleration + applies to. Empty means match all taint keys. If the + key is empty, operator must be Exists; this combination + means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship + to the value. Valid operators are Exists and Equal. + Defaults to Equal. Exists is equivalent to wildcard + for value, so that a pod can tolerate all taints of + a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period + of time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the + taint forever (do not evict). Zero and negative values + will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration + matches to. If the operator is Exists, the value should + be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + securityOptions: + description: Options to configure the Security Contexts for the + Cryostat report generator. + properties: + podSecurityContext: + description: Security Context to apply to the Cryostat report + generator pod. + properties: + fsGroup: + description: "A special supplemental group that applies + to all containers in a pod. Some volume types allow + the Kubelet to change the ownership of that volume to + be owned by the pod: \n 1. The owning GID will be the + FSGroup 2. The setgid bit is set (new files created + in the volume will be owned by FSGroup) 3. The permission + bits are OR'd with rw-rw---- \n If unset, the Kubelet + will not modify the ownership and permissions of any + volume. Note that this field cannot be set when spec.os.name + is windows." + format: int64 + type: integer + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of + changing ownership and permission of the volume before + being exposed inside Pod. This field will only apply + to volume types which support fsGroup based ownership(and + permissions). It will have no effect on ephemeral volume + types such as: secret, configmaps and emptydir. Valid + values are "OnRootMismatch" and "Always". If not specified, + "Always" is used. Note that this field cannot be set + when spec.os.name is windows.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this + field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if it + does. If unset or false, no such validation will be + performed. May also be set in SecurityContext. If set + in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all + containers. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this + field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by the containers + in this pod. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. The + profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's + configured seccomp profile location. Must only be + set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n Localhost + - a profile defined in a file on the node should + be used. RuntimeDefault - the container runtime + default profile should be used. Unconfined - no + profile should be applied." + type: string + required: + - type + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's + primary GID. If unspecified, no groups will be added + to any container. Note that this field cannot be set + when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls + used for the pod. Pods with unsupported sysctls (by + the container runtime) might fail to launch. Note that + this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be + set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options within a + container's SecurityContext will be used. If set in + both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored by + components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the Pod. + All of a Pod's containers must have the same effective + HostProcess value (it is not allowed to have a mix + of HostProcess containers and non-HostProcess containers). In + addition, if HostProcess is true then HostNetwork + must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + reportsSecurityContext: + description: Security Context to apply to the Cryostat report + generator container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag + will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root + filesystem. Default is false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if it + does. If unset or false, no such validation will be + performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. + Note that this field cannot be set when spec.os.name + is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. The + profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's + configured seccomp profile location. Must only be + set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n Localhost + - a profile defined in a file on the node should + be used. RuntimeDefault - the container runtime + default profile should be used. Unconfined - no + profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored by + components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the Pod. + All of a Pod's containers must have the same effective + HostProcess value (it is not allowed to have a mix + of HostProcess containers and non-HostProcess containers). In + addition, if HostProcess is true then HostNetwork + must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + type: object + subProcessMaxHeapSize: + description: When zero report sidecar replicas are requested, + SubProcessMaxHeapSize configures the maximum heap size of the + basic subprocess report generator in MiB. The default heap size + is `200` (MiB). + format: int32 + type: integer + type: object + resources: + description: Resource requirements for the Cryostat deployment. + properties: + coreResources: + description: Resource requirements for the Cryostat application. + If specifying a memory limit, at least 768MiB is recommended. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + dataSourceResources: + description: Resource requirements for the JFR Data Source container. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + grafanaResources: + description: Resource requirements for the Grafana container. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + type: object + schedulingOptions: + description: Options to configure scheduling for the Cryostat deployment + properties: + affinity: + description: Affinity rules for scheduling Cryostat pods. + properties: + nodeAffinity: + description: 'Node affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: 'Pod affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: 'Pod anti-affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: 'Label selector used to schedule a Cryostat pod to + a node. See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + tolerations: + description: 'Tolerations to allow scheduling of Cryostat pods + to tainted nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + securityOptions: + description: Options to configure the Security Contexts for the Cryostat + application. + properties: + coreSecurityContext: + description: Security Context to apply to the Cryostat application + container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a + process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag will + be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be set + when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the + container runtime. Note that this field cannot be set when + spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes in + privileged containers are essentially equivalent to root + on the host. Defaults to false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use + for the containers. The default is DefaultProcMount which + uses the container runtime defaults for readonly paths and + masked paths. This requires the ProcMountType feature flag + to be enabled. Note that this field cannot be set when spec.os.name + is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when + spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. Note + that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options from the PodSecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is + linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + dataSourceSecurityContext: + description: Security Context to apply to the JFR Data Source + container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a + process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag will + be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be set + when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the + container runtime. Note that this field cannot be set when + spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes in + privileged containers are essentially equivalent to root + on the host. Defaults to false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use + for the containers. The default is DefaultProcMount which + uses the container runtime defaults for readonly paths and + masked paths. This requires the ProcMountType feature flag + to be enabled. Note that this field cannot be set when spec.os.name + is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when + spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. Note + that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options from the PodSecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is + linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + grafanaSecurityContext: + description: Security Context to apply to the Grafana container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a + process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag will + be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be set + when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the + container runtime. Note that this field cannot be set when + spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes in + privileged containers are essentially equivalent to root + on the host. Defaults to false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use + for the containers. The default is DefaultProcMount which + uses the container runtime defaults for readonly paths and + masked paths. This requires the ProcMountType feature flag + to be enabled. Note that this field cannot be set when spec.os.name + is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when + spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. Note + that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options from the PodSecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is + linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + podSecurityContext: + description: Security Context to apply to the Cryostat pod. + properties: + fsGroup: + description: "A special supplemental group that applies to + all containers in a pod. Some volume types allow the Kubelet + to change the ownership of that volume to be owned by the + pod: \n 1. The owning GID will be the FSGroup 2. The setgid + bit is set (new files created in the volume will be owned + by FSGroup) 3. The permission bits are OR'd with rw-rw---- + \n If unset, the Kubelet will not modify the ownership and + permissions of any volume. Note that this field cannot be + set when spec.os.name is windows." + format: int64 + type: integer + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of changing + ownership and permission of the volume before being exposed + inside Pod. This field will only apply to volume types which + support fsGroup based ownership(and permissions). It will + have no effect on ephemeral volume types such as: secret, + configmaps and emptydir. Valid values are "OnRootMismatch" + and "Always". If not specified, "Always" is used. Note that + this field cannot be set when spec.os.name is windows.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this field + cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in SecurityContext. If set + in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence for that container. + Note that this field cannot be set when spec.os.name is + windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + SecurityContext. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by the containers + in this pod. Note that this field cannot be set when spec.os.name + is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's primary + GID. If unspecified, no groups will be added to any container. + Note that this field cannot be set when spec.os.name is + windows. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls used + for the pod. Pods with unsupported sysctls (by the container + runtime) might fail to launch. Note that this field cannot + be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options within a container's + SecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + type: object + serviceOptions: + description: Options to customize the services created for the Cryostat + application and Grafana dashboard. + properties: + coreConfig: + description: Specification for the service responsible for the + Cryostat application. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the service during its + creation. + type: object + httpPort: + description: HTTP port number for the Cryostat application + service. Defaults to 8181. + format: int32 + type: integer + jmxPort: + description: Remote JMX port number for the Cryostat application + service. Defaults to 9091. + format: int32 + type: integer + labels: + additionalProperties: + type: string + description: Labels to add to the service during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + serviceType: + description: Type of service to create. Defaults to "ClusterIP". + type: string + type: object + grafanaConfig: + description: Specification for the service responsible for the + Cryostat Grafana dashboard. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the service during its + creation. + type: object + httpPort: + description: HTTP port number for the Grafana dashboard service. + Defaults to 3000. + format: int32 + type: integer + labels: + additionalProperties: + type: string + description: Labels to add to the service during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + serviceType: + description: Type of service to create. Defaults to "ClusterIP". + type: string + type: object + reportsConfig: + description: Specification for the service responsible for the + cryostat-reports sidecars. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the service during its + creation. + type: object + httpPort: + description: HTTP port number for the cryostat-reports service. + Defaults to 10000. + format: int32 + type: integer + labels: + additionalProperties: + type: string + description: Labels to add to the service during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + serviceType: + description: Type of service to create. Defaults to "ClusterIP". + type: string + type: object + type: object + storageOptions: + description: Options to customize the storage for Flight Recordings + and Templates. + properties: + emptyDir: + description: Configuration for an EmptyDir to be created by the + operator instead of a PVC. + properties: + enabled: + description: When enabled, Cryostat will use EmptyDir volumes + instead of a Persistent Volume Claim. Any PVC configurations + will be ignored. + type: boolean + medium: + description: Unless specified, the emptyDir volume will be + mounted on the same storage medium backing the node. Setting + this field to "Memory" will mount the emptyDir on a tmpfs + (RAM-backed filesystem). + type: string + sizeLimit: + description: The maximum memory limit for the emptyDir. Default + is unbounded. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + type: string + type: object + pvc: + description: Configuration for the Persistent Volume Claim to + be created by the operator. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Persistent Volume Claim + during its creation. + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Persistent Volume Claim + during its creation. The label with key "app" is reserved + for use by the operator. + type: object + spec: + description: Spec for a Persistent Volume Claim, whose options + will override the defaults used by the operator. Unless + overriden, the PVC will be created with the default Storage + Class and 500MiB of storage. Once the operator has created + the PVC, changes to this field have no effect. + properties: + accessModes: + description: 'accessModes contains the desired access + modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'dataSource field can be used to specify + either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) If the provisioner + or an external controller can support the specified + data source, it will create a new volume based on the + contents of the specified data source. If the AnyVolumeDataSource + feature gate is enabled, this field will always have + the same contents as the DataSourceRef field.' + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'dataSourceRef specifies the object from + which to populate the volume with data, if a non-empty + volume is desired. This may be any local object from + a non-empty API group (non core object) or a PersistentVolumeClaim + object. When this field is specified, volume binding + will only succeed if the type of the specified object + matches some installed volume populator or dynamic provisioner. + This field will replace the functionality of the DataSource + field and as such if both fields are non-empty, they + must have the same value. For backwards compatibility, + both fields (DataSource and DataSourceRef) will be set + to the same value automatically if one of them is empty + and the other is non-empty. There are two important + differences between DataSource and DataSourceRef: * + While DataSource only allows two specific types of objects, + DataSourceRef allows any non-core object, as well as + PersistentVolumeClaim objects. * While DataSource ignores + disallowed values (dropping them), DataSourceRef preserves + all values, and generates an error if a disallowed value + is specified. (Beta) Using this field requires the AnyVolumeDataSource + feature gate to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'resources represents the minimum resources + the volume should have. If RecoverVolumeExpansionFailure + feature is enabled users are allowed to specify resource + requirements that are lower than previous value but + must still be higher than capacity recorded in the status + field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount + of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: selector is a label query over volumes to + consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values + array must be non-empty. If the operator is + Exists or DoesNotExist, the values array must + be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'storageClassName is the name of the StorageClass + required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is + required by the claim. Value of Filesystem is implied + when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference to the + PersistentVolume backing this claim. + type: string + type: object + type: object + type: object + targetDiscoveryOptions: + description: Options to configure the Cryostat application's target + discovery mechanisms. + properties: + builtInDiscoveryDisabled: + description: When true, the Cryostat application will disable + the built-in discovery mechanisms. Defaults to false + type: boolean + type: object + targetNamespaces: + description: List of namespaces whose workloads Cryostat should be + permitted to access and profile. Defaults to `spec.installNamespace`. + items: + type: string + type: array + trustedCertSecrets: + description: List of TLS certificates to trust when connecting to + targets. + items: + properties: + certificateKey: + description: Key within secret containing the certificate. + type: string + secretName: + description: Name of secret in the local namespace. + type: string + required: + - secretName + type: object + type: array + required: + - installNamespace + - minimal + type: object + status: + description: ClusterCryostatStatus defines the observed state of ClusterCryostat. + properties: + applicationUrl: + description: Address of the deployed Cryostat web application. + type: string + conditions: + description: Conditions of the components managed by the Cryostat + Operator. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a foo's + current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + grafanaSecret: + description: Name of the Secret containing the generated Grafana credentials. + type: string + required: + - applicationUrl + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index a5da61ab2..56398625c 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -16,6 +16,12 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: + - jsonPath: .status.applicationUrl + name: Application URL + type: string + - jsonPath: .status.grafanaSecret + name: Grafana Secret + type: string - jsonPath: .status.applicationUrl name: Application URL type: string @@ -4496,3 +4502,4475 @@ spec: storage: true subresources: status: {} + - name: v1beta1 + schema: + openAPIV3Schema: + description: Cryostat contains configuration options for controlling the Deployment + of the Cryostat application and its related components. A Cryostat instance + must be created to instruct the operator to deploy the Cryostat application. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CryostatSpec defines the desired state of Cryostat. + properties: + authProperties: + description: Override default authorization properties for Cryostat + on OpenShift. + properties: + clusterRoleName: + description: 'Name of the ClusterRole to use when Cryostat requests + a role-scoped OAuth token. This ClusterRole should contain permissions + for all Kubernetes objects listed in custom permission mapping. + More details: https://docs.openshift.com/container-platform/4.11/authentication/tokens-scoping.html#scoping-tokens-role-scope_configuring-internal-oauth' + type: string + configMapName: + description: Name of config map in the local namespace. + type: string + filename: + description: Filename within config map containing the resource + mapping. + type: string + required: + - clusterRoleName + - configMapName + - filename + type: object + enableCertManager: + description: Use cert-manager to secure in-cluster communication between + Cryostat components. Requires cert-manager to be installed. + type: boolean + eventTemplates: + description: List of Flight Recorder Event Templates to preconfigure + in Cryostat. + items: + description: A ConfigMap containing a .jfc template file. + properties: + configMapName: + description: Name of config map in the local namespace. + type: string + filename: + description: Filename within config map containing the template + file. + type: string + required: + - configMapName + - filename + type: object + type: array + jmxCacheOptions: + description: Options to customize the JMX target connections cache + for the Cryostat application. + properties: + targetCacheSize: + description: The maximum number of JMX connections to cache. Use + `-1` for an unlimited cache size (TTL expiration only). Defaults + to `-1`. + format: int32 + minimum: -1 + type: integer + targetCacheTTL: + description: The time to live (in seconds) for cached JMX connections. + Defaults to `10`. + format: int32 + minimum: 1 + type: integer + type: object + jmxCredentialsDatabaseOptions: + description: Options to configure the Cryostat application's JMX credentials + database. + properties: + databaseSecretName: + description: Name of the secret containing the password to encrypt + JMX credentials database. + type: string + type: object + maxWsConnections: + description: The maximum number of WebSocket client connections allowed + (minimum 1, default unlimited). + format: int32 + minimum: 1 + type: integer + minimal: + description: Deploy a pared-down Cryostat instance with no Grafana + Dashboard or JFR Data Source. + type: boolean + networkOptions: + description: Options to control how the operator exposes the application + outside of the cluster, such as using an Ingress or Route. + properties: + commandConfig: + description: "Specifications for how to expose the Cryostat command + service, which serves the WebSocket command channel. \n Deprecated: + CommandConfig is no longer used." + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Ingress or Route during + its creation. + type: object + ingressSpec: + description: Configuration for an Ingress object. Currently + subpaths are not supported, so unique hosts must be specified + (if a single external IP is being used) to differentiate + between ingresses/services. + properties: + defaultBackend: + description: DefaultBackend is the backend that should + handle requests that don't match any rule. If Rules + are not specified, DefaultBackend must be specified. + If DefaultBackend is not set, the handling of requests + that do not match any of the rules will be up to the + Ingress controller. + properties: + resource: + description: Resource is an ObjectRef to another Kubernetes + resource in the namespace of the Ingress object. + If resource is specified, a service.Name and service.Port + must not be specified. This is a mutually exclusive + setting with "Service". + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is + required. + type: string + kind: + description: Kind is the type of resource being + referenced + type: string + name: + description: Name is the name of resource being + referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a Service as a Backend. + This is a mutually exclusive setting with "Resource". + properties: + name: + description: Name is the referenced service. The + service must exist in the same namespace as + the Ingress object. + type: string + port: + description: Port of the referenced service. A + port name or port number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name of the port + on the Service. This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the numerical port + number (e.g. 80) on the Service. This is + a mutually exclusive setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + ingressClassName: + description: IngressClassName is the name of the IngressClass + cluster resource. The associated IngressClass defines + which controller will implement the resource. This replaces + the deprecated `kubernetes.io/ingress.class` annotation. + For backwards compatibility, when that annotation is + set, it must be given precedence over this field. The + controller may emit a warning if the field and annotation + have different values. Implementations of this API should + ignore Ingresses without a class specified. An IngressClass + resource may be marked as default, which can be used + to set a default value for this field. For more information, + refer to the IngressClass documentation. + type: string + rules: + description: A list of host rules used to configure the + Ingress. If unspecified, or no rule matches, all traffic + is sent to the default backend. + items: + description: IngressRule represents the rules mapping + the paths under a specified host to the related backend + services. Incoming requests are first evaluated for + a host match, then routed to the backend associated + with the matching IngressRuleValue. + properties: + host: + description: "Host is the fully qualified domain + name of a network host, as defined by RFC 3986. + Note the following deviations from the \"host\" + part of the URI as defined in RFC 3986: 1. IPs + are not allowed. Currently an IngressRuleValue + can only apply to the IP in the Spec of the parent + Ingress. 2. The `:` delimiter is not respected + because ports are not allowed. Currently the port + of an Ingress is implicitly :80 for http and :443 + for https. Both these may change in the future. + Incoming requests are matched against the host + before the IngressRuleValue. If the host is unspecified, + the Ingress routes all traffic based on the specified + IngressRuleValue. \n Host can be \"precise\" which + is a domain name without the terminating dot of + a network host (e.g. \"foo.bar.com\") or \"wildcard\", + which is a domain name prefixed with a single + wildcard label (e.g. \"*.foo.com\"). The wildcard + character '*' must appear by itself as the first + DNS label and matches only a single label. You + cannot have a wildcard label by itself (e.g. Host + == \"*\"). Requests will be matched against the + Host field in the following way: 1. If Host is + precise, the request matches this rule if the + http host header is equal to Host. 2. If Host + is a wildcard, then the request matches this rule + if the http host header is to equal to the suffix + (removing the first label) of the wildcard rule." + type: string + http: + description: 'HTTPIngressRuleValue is a list of + http selectors pointing to backends. In the example: + http:///? -> backend where + where parts of the url correspond to RFC 3986, + this resource will be used to match against everything + after the last ''/'' and before the first ''?'' + or ''#''.' + properties: + paths: + description: A collection of paths that map + requests to backends. + items: + description: HTTPIngressPath associates a + path with a backend. Incoming urls matching + the path are forwarded to the backend. + properties: + backend: + description: Backend defines the referenced + service endpoint to which the traffic + will be forwarded to. + properties: + resource: + description: Resource is an ObjectRef + to another Kubernetes resource in + the namespace of the Ingress object. + If resource is specified, a service.Name + and service.Port must not be specified. + This is a mutually exclusive setting + with "Service". + properties: + apiGroup: + description: APIGroup is the group + for the resource being referenced. + If APIGroup is not specified, + the specified Kind must be in + the core API group. For any + other third-party types, APIGroup + is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a + Service as a Backend. This is a + mutually exclusive setting with + "Resource". + properties: + name: + description: Name is the referenced + service. The service must exist + in the same namespace as the + Ingress object. + type: string + port: + description: Port of the referenced + service. A port name or port + number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name + of the port on the Service. + This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the + numerical port number (e.g. + 80) on the Service. This + is a mutually exclusive + setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + path: + description: Path is matched against the + path of an incoming request. Currently + it can contain characters disallowed + from the conventional "path" part of + a URL as defined by RFC 3986. Paths + must begin with a '/' and must be present + when using PathType with value "Exact" + or "Prefix". + type: string + pathType: + description: 'PathType determines the + interpretation of the Path matching. + PathType can be one of the following + values: * Exact: Matches the URL path + exactly. * Prefix: Matches based on + a URL path prefix split by ''/''. Matching + is done on a path element by element + basis. A path element refers is the + list of labels in the path split by + the ''/'' separator. A request is a + match for path p if every p is an element-wise + prefix of p of the request path. Note + that if the last element of the path + is a substring of the last element in + request path, it is not a match (e.g. + /foo/bar matches /foo/bar/baz, but does + not match /foo/barbaz). * ImplementationSpecific: + Interpretation of the Path matching + is up to the IngressClass. Implementations + can treat this as a separate PathType + or treat it identically to Prefix or + Exact path types. Implementations are + required to support all path types.' + type: string + required: + - backend + - pathType + type: object + type: array + x-kubernetes-list-type: atomic + required: + - paths + type: object + type: object + type: array + x-kubernetes-list-type: atomic + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Ingress or Route during + its creation. The label with key "app" is reserved for use + by the operator. + type: object + type: object + coreConfig: + description: Specifications for how to expose the Cryostat service, + which serves the Cryostat application. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Ingress or Route during + its creation. + type: object + ingressSpec: + description: Configuration for an Ingress object. Currently + subpaths are not supported, so unique hosts must be specified + (if a single external IP is being used) to differentiate + between ingresses/services. + properties: + defaultBackend: + description: DefaultBackend is the backend that should + handle requests that don't match any rule. If Rules + are not specified, DefaultBackend must be specified. + If DefaultBackend is not set, the handling of requests + that do not match any of the rules will be up to the + Ingress controller. + properties: + resource: + description: Resource is an ObjectRef to another Kubernetes + resource in the namespace of the Ingress object. + If resource is specified, a service.Name and service.Port + must not be specified. This is a mutually exclusive + setting with "Service". + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is + required. + type: string + kind: + description: Kind is the type of resource being + referenced + type: string + name: + description: Name is the name of resource being + referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a Service as a Backend. + This is a mutually exclusive setting with "Resource". + properties: + name: + description: Name is the referenced service. The + service must exist in the same namespace as + the Ingress object. + type: string + port: + description: Port of the referenced service. A + port name or port number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name of the port + on the Service. This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the numerical port + number (e.g. 80) on the Service. This is + a mutually exclusive setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + ingressClassName: + description: IngressClassName is the name of the IngressClass + cluster resource. The associated IngressClass defines + which controller will implement the resource. This replaces + the deprecated `kubernetes.io/ingress.class` annotation. + For backwards compatibility, when that annotation is + set, it must be given precedence over this field. The + controller may emit a warning if the field and annotation + have different values. Implementations of this API should + ignore Ingresses without a class specified. An IngressClass + resource may be marked as default, which can be used + to set a default value for this field. For more information, + refer to the IngressClass documentation. + type: string + rules: + description: A list of host rules used to configure the + Ingress. If unspecified, or no rule matches, all traffic + is sent to the default backend. + items: + description: IngressRule represents the rules mapping + the paths under a specified host to the related backend + services. Incoming requests are first evaluated for + a host match, then routed to the backend associated + with the matching IngressRuleValue. + properties: + host: + description: "Host is the fully qualified domain + name of a network host, as defined by RFC 3986. + Note the following deviations from the \"host\" + part of the URI as defined in RFC 3986: 1. IPs + are not allowed. Currently an IngressRuleValue + can only apply to the IP in the Spec of the parent + Ingress. 2. The `:` delimiter is not respected + because ports are not allowed. Currently the port + of an Ingress is implicitly :80 for http and :443 + for https. Both these may change in the future. + Incoming requests are matched against the host + before the IngressRuleValue. If the host is unspecified, + the Ingress routes all traffic based on the specified + IngressRuleValue. \n Host can be \"precise\" which + is a domain name without the terminating dot of + a network host (e.g. \"foo.bar.com\") or \"wildcard\", + which is a domain name prefixed with a single + wildcard label (e.g. \"*.foo.com\"). The wildcard + character '*' must appear by itself as the first + DNS label and matches only a single label. You + cannot have a wildcard label by itself (e.g. Host + == \"*\"). Requests will be matched against the + Host field in the following way: 1. If Host is + precise, the request matches this rule if the + http host header is equal to Host. 2. If Host + is a wildcard, then the request matches this rule + if the http host header is to equal to the suffix + (removing the first label) of the wildcard rule." + type: string + http: + description: 'HTTPIngressRuleValue is a list of + http selectors pointing to backends. In the example: + http:///? -> backend where + where parts of the url correspond to RFC 3986, + this resource will be used to match against everything + after the last ''/'' and before the first ''?'' + or ''#''.' + properties: + paths: + description: A collection of paths that map + requests to backends. + items: + description: HTTPIngressPath associates a + path with a backend. Incoming urls matching + the path are forwarded to the backend. + properties: + backend: + description: Backend defines the referenced + service endpoint to which the traffic + will be forwarded to. + properties: + resource: + description: Resource is an ObjectRef + to another Kubernetes resource in + the namespace of the Ingress object. + If resource is specified, a service.Name + and service.Port must not be specified. + This is a mutually exclusive setting + with "Service". + properties: + apiGroup: + description: APIGroup is the group + for the resource being referenced. + If APIGroup is not specified, + the specified Kind must be in + the core API group. For any + other third-party types, APIGroup + is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a + Service as a Backend. This is a + mutually exclusive setting with + "Resource". + properties: + name: + description: Name is the referenced + service. The service must exist + in the same namespace as the + Ingress object. + type: string + port: + description: Port of the referenced + service. A port name or port + number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name + of the port on the Service. + This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the + numerical port number (e.g. + 80) on the Service. This + is a mutually exclusive + setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + path: + description: Path is matched against the + path of an incoming request. Currently + it can contain characters disallowed + from the conventional "path" part of + a URL as defined by RFC 3986. Paths + must begin with a '/' and must be present + when using PathType with value "Exact" + or "Prefix". + type: string + pathType: + description: 'PathType determines the + interpretation of the Path matching. + PathType can be one of the following + values: * Exact: Matches the URL path + exactly. * Prefix: Matches based on + a URL path prefix split by ''/''. Matching + is done on a path element by element + basis. A path element refers is the + list of labels in the path split by + the ''/'' separator. A request is a + match for path p if every p is an element-wise + prefix of p of the request path. Note + that if the last element of the path + is a substring of the last element in + request path, it is not a match (e.g. + /foo/bar matches /foo/bar/baz, but does + not match /foo/barbaz). * ImplementationSpecific: + Interpretation of the Path matching + is up to the IngressClass. Implementations + can treat this as a separate PathType + or treat it identically to Prefix or + Exact path types. Implementations are + required to support all path types.' + type: string + required: + - backend + - pathType + type: object + type: array + x-kubernetes-list-type: atomic + required: + - paths + type: object + type: object + type: array + x-kubernetes-list-type: atomic + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Ingress or Route during + its creation. The label with key "app" is reserved for use + by the operator. + type: object + type: object + grafanaConfig: + description: Specifications for how to expose Cryostat's Grafana + service, which serves the Grafana dashboard. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Ingress or Route during + its creation. + type: object + ingressSpec: + description: Configuration for an Ingress object. Currently + subpaths are not supported, so unique hosts must be specified + (if a single external IP is being used) to differentiate + between ingresses/services. + properties: + defaultBackend: + description: DefaultBackend is the backend that should + handle requests that don't match any rule. If Rules + are not specified, DefaultBackend must be specified. + If DefaultBackend is not set, the handling of requests + that do not match any of the rules will be up to the + Ingress controller. + properties: + resource: + description: Resource is an ObjectRef to another Kubernetes + resource in the namespace of the Ingress object. + If resource is specified, a service.Name and service.Port + must not be specified. This is a mutually exclusive + setting with "Service". + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is + required. + type: string + kind: + description: Kind is the type of resource being + referenced + type: string + name: + description: Name is the name of resource being + referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a Service as a Backend. + This is a mutually exclusive setting with "Resource". + properties: + name: + description: Name is the referenced service. The + service must exist in the same namespace as + the Ingress object. + type: string + port: + description: Port of the referenced service. A + port name or port number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name of the port + on the Service. This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the numerical port + number (e.g. 80) on the Service. This is + a mutually exclusive setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + ingressClassName: + description: IngressClassName is the name of the IngressClass + cluster resource. The associated IngressClass defines + which controller will implement the resource. This replaces + the deprecated `kubernetes.io/ingress.class` annotation. + For backwards compatibility, when that annotation is + set, it must be given precedence over this field. The + controller may emit a warning if the field and annotation + have different values. Implementations of this API should + ignore Ingresses without a class specified. An IngressClass + resource may be marked as default, which can be used + to set a default value for this field. For more information, + refer to the IngressClass documentation. + type: string + rules: + description: A list of host rules used to configure the + Ingress. If unspecified, or no rule matches, all traffic + is sent to the default backend. + items: + description: IngressRule represents the rules mapping + the paths under a specified host to the related backend + services. Incoming requests are first evaluated for + a host match, then routed to the backend associated + with the matching IngressRuleValue. + properties: + host: + description: "Host is the fully qualified domain + name of a network host, as defined by RFC 3986. + Note the following deviations from the \"host\" + part of the URI as defined in RFC 3986: 1. IPs + are not allowed. Currently an IngressRuleValue + can only apply to the IP in the Spec of the parent + Ingress. 2. The `:` delimiter is not respected + because ports are not allowed. Currently the port + of an Ingress is implicitly :80 for http and :443 + for https. Both these may change in the future. + Incoming requests are matched against the host + before the IngressRuleValue. If the host is unspecified, + the Ingress routes all traffic based on the specified + IngressRuleValue. \n Host can be \"precise\" which + is a domain name without the terminating dot of + a network host (e.g. \"foo.bar.com\") or \"wildcard\", + which is a domain name prefixed with a single + wildcard label (e.g. \"*.foo.com\"). The wildcard + character '*' must appear by itself as the first + DNS label and matches only a single label. You + cannot have a wildcard label by itself (e.g. Host + == \"*\"). Requests will be matched against the + Host field in the following way: 1. If Host is + precise, the request matches this rule if the + http host header is equal to Host. 2. If Host + is a wildcard, then the request matches this rule + if the http host header is to equal to the suffix + (removing the first label) of the wildcard rule." + type: string + http: + description: 'HTTPIngressRuleValue is a list of + http selectors pointing to backends. In the example: + http:///? -> backend where + where parts of the url correspond to RFC 3986, + this resource will be used to match against everything + after the last ''/'' and before the first ''?'' + or ''#''.' + properties: + paths: + description: A collection of paths that map + requests to backends. + items: + description: HTTPIngressPath associates a + path with a backend. Incoming urls matching + the path are forwarded to the backend. + properties: + backend: + description: Backend defines the referenced + service endpoint to which the traffic + will be forwarded to. + properties: + resource: + description: Resource is an ObjectRef + to another Kubernetes resource in + the namespace of the Ingress object. + If resource is specified, a service.Name + and service.Port must not be specified. + This is a mutually exclusive setting + with "Service". + properties: + apiGroup: + description: APIGroup is the group + for the resource being referenced. + If APIGroup is not specified, + the specified Kind must be in + the core API group. For any + other third-party types, APIGroup + is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a + Service as a Backend. This is a + mutually exclusive setting with + "Resource". + properties: + name: + description: Name is the referenced + service. The service must exist + in the same namespace as the + Ingress object. + type: string + port: + description: Port of the referenced + service. A port name or port + number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name + of the port on the Service. + This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the + numerical port number (e.g. + 80) on the Service. This + is a mutually exclusive + setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + path: + description: Path is matched against the + path of an incoming request. Currently + it can contain characters disallowed + from the conventional "path" part of + a URL as defined by RFC 3986. Paths + must begin with a '/' and must be present + when using PathType with value "Exact" + or "Prefix". + type: string + pathType: + description: 'PathType determines the + interpretation of the Path matching. + PathType can be one of the following + values: * Exact: Matches the URL path + exactly. * Prefix: Matches based on + a URL path prefix split by ''/''. Matching + is done on a path element by element + basis. A path element refers is the + list of labels in the path split by + the ''/'' separator. A request is a + match for path p if every p is an element-wise + prefix of p of the request path. Note + that if the last element of the path + is a substring of the last element in + request path, it is not a match (e.g. + /foo/bar matches /foo/bar/baz, but does + not match /foo/barbaz). * ImplementationSpecific: + Interpretation of the Path matching + is up to the IngressClass. Implementations + can treat this as a separate PathType + or treat it identically to Prefix or + Exact path types. Implementations are + required to support all path types.' + type: string + required: + - backend + - pathType + type: object + type: array + x-kubernetes-list-type: atomic + required: + - paths + type: object + type: object + type: array + x-kubernetes-list-type: atomic + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Ingress or Route during + its creation. The label with key "app" is reserved for use + by the operator. + type: object + type: object + type: object + reportOptions: + description: Options to configure Cryostat Automated Report Analysis. + properties: + replicas: + description: The number of report sidecar replica containers to + deploy. Each replica can service one report generation request + at a time. + format: int32 + type: integer + resources: + description: The resources allocated to each sidecar replica. + A replica with more resources can handle larger input recordings + and will process them faster. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + schedulingOptions: + description: Options to configure scheduling for the reports deployment + properties: + affinity: + description: Affinity rules for scheduling Cryostat pods. + properties: + nodeAffinity: + description: 'Node affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. The + node that is most preferred is the one with the + greatest sum of weights, i.e. for each node that + meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements + of this field and adding "weight" to the sum if + the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term + matches all objects with implicit weight 0 (i.e. + it's a no-op). A null preferred scheduling term + matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching + the corresponding nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified + by this field are not met at scheduling time, the + pod will not be scheduled onto the node. If the + affinity requirements specified by this field cease + to be met at some point during pod execution (e.g. + due to an update), the system may or may not try + to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector + terms. The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them + are ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: 'Pod affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. The + node that is most preferred is the one with the + greatest sum of weights, i.e. for each node that + meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements + of this field and adding "weight" to the sum if + the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum + are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of + resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set + of namespaces that the term applies to. + The term is applied to the union of the + namespaces selected by this field and + the ones listed in the namespaces field. + null selector and null or empty namespaces + list means "this pod's namespace". An + empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static + list of namespace names that the term + applies to. The term is applied to the + union of the namespaces listed in this + field and the ones selected by namespaceSelector. + null or empty namespaces list and null + namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose + value of the label with key topologyKey + matches that of any node on which any + of the selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching + the corresponding podAffinityTerm, in the + range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified + by this field are not met at scheduling time, the + pod will not be scheduled onto the node. If the + affinity requirements specified by this field cease + to be met at some point during pod execution (e.g. + due to a pod label update), the system may or may + not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes + corresponding to each podAffinityTerm are intersected, + i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those + matching the labelSelector relative to the given + namespace(s)) that this pod should be co-located + (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node + whose value of the label with key + matches that of any node on which a pod of the + set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: 'Pod anti-affinity scheduling rules for a + Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. The + node that is most preferred is the one with the + greatest sum of weights, i.e. for each node that + meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity + expressions, etc.), compute a sum by iterating through + the elements of this field and adding "weight" to + the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum + are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of + resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set + of namespaces that the term applies to. + The term is applied to the union of the + namespaces selected by this field and + the ones listed in the namespaces field. + null selector and null or empty namespaces + list means "this pod's namespace". An + empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static + list of namespace names that the term + applies to. The term is applied to the + union of the namespaces listed in this + field and the ones selected by namespaceSelector. + null or empty namespaces list and null + namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose + value of the label with key topologyKey + matches that of any node on which any + of the selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching + the corresponding podAffinityTerm, in the + range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the + pod will not be scheduled onto the node. If the + anti-affinity requirements specified by this field + cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may + or may not try to eventually evict the pod from + its node. When there are multiple elements, the + lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those + matching the labelSelector relative to the given + namespace(s)) that this pod should be co-located + (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node + whose value of the label with key + matches that of any node on which a pod of the + set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: 'Label selector used to schedule a Cryostat pod + to a node. See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + tolerations: + description: 'Tolerations to allow scheduling of Cryostat + pods to tainted nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, + allowed values are NoSchedule, PreferNoSchedule and + NoExecute. + type: string + key: + description: Key is the taint key that the toleration + applies to. Empty means match all taint keys. If the + key is empty, operator must be Exists; this combination + means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship + to the value. Valid operators are Exists and Equal. + Defaults to Equal. Exists is equivalent to wildcard + for value, so that a pod can tolerate all taints of + a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period + of time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the + taint forever (do not evict). Zero and negative values + will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration + matches to. If the operator is Exists, the value should + be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + securityOptions: + description: Options to configure the Security Contexts for the + Cryostat report generator. + properties: + podSecurityContext: + description: Security Context to apply to the Cryostat report + generator pod. + properties: + fsGroup: + description: "A special supplemental group that applies + to all containers in a pod. Some volume types allow + the Kubelet to change the ownership of that volume to + be owned by the pod: \n 1. The owning GID will be the + FSGroup 2. The setgid bit is set (new files created + in the volume will be owned by FSGroup) 3. The permission + bits are OR'd with rw-rw---- \n If unset, the Kubelet + will not modify the ownership and permissions of any + volume. Note that this field cannot be set when spec.os.name + is windows." + format: int64 + type: integer + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of + changing ownership and permission of the volume before + being exposed inside Pod. This field will only apply + to volume types which support fsGroup based ownership(and + permissions). It will have no effect on ephemeral volume + types such as: secret, configmaps and emptydir. Valid + values are "OnRootMismatch" and "Always". If not specified, + "Always" is used. Note that this field cannot be set + when spec.os.name is windows.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this + field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if it + does. If unset or false, no such validation will be + performed. May also be set in SecurityContext. If set + in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all + containers. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this + field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by the containers + in this pod. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. The + profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's + configured seccomp profile location. Must only be + set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n Localhost + - a profile defined in a file on the node should + be used. RuntimeDefault - the container runtime + default profile should be used. Unconfined - no + profile should be applied." + type: string + required: + - type + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's + primary GID. If unspecified, no groups will be added + to any container. Note that this field cannot be set + when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls + used for the pod. Pods with unsupported sysctls (by + the container runtime) might fail to launch. Note that + this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be + set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options within a + container's SecurityContext will be used. If set in + both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored by + components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the Pod. + All of a Pod's containers must have the same effective + HostProcess value (it is not allowed to have a mix + of HostProcess containers and non-HostProcess containers). In + addition, if HostProcess is true then HostNetwork + must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + reportsSecurityContext: + description: Security Context to apply to the Cryostat report + generator container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag + will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root + filesystem. Default is false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if it + does. If unset or false, no such validation will be + performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. + Note that this field cannot be set when spec.os.name + is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. The + profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's + configured seccomp profile location. Must only be + set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n Localhost + - a profile defined in a file on the node should + be used. RuntimeDefault - the container runtime + default profile should be used. Unconfined - no + profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored by + components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the Pod. + All of a Pod's containers must have the same effective + HostProcess value (it is not allowed to have a mix + of HostProcess containers and non-HostProcess containers). In + addition, if HostProcess is true then HostNetwork + must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + type: object + subProcessMaxHeapSize: + description: When zero report sidecar replicas are requested, + SubProcessMaxHeapSize configures the maximum heap size of the + basic subprocess report generator in MiB. The default heap size + is `200` (MiB). + format: int32 + type: integer + type: object + resources: + description: Resource requirements for the Cryostat deployment. + properties: + coreResources: + description: Resource requirements for the Cryostat application. + If specifying a memory limit, at least 768MiB is recommended. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + dataSourceResources: + description: Resource requirements for the JFR Data Source container. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + grafanaResources: + description: Resource requirements for the Grafana container. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + type: object + schedulingOptions: + description: Options to configure scheduling for the Cryostat deployment + properties: + affinity: + description: Affinity rules for scheduling Cryostat pods. + properties: + nodeAffinity: + description: 'Node affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: 'Pod affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: 'Pod anti-affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: 'Label selector used to schedule a Cryostat pod to + a node. See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + tolerations: + description: 'Tolerations to allow scheduling of Cryostat pods + to tainted nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + securityOptions: + description: Options to configure the Security Contexts for the Cryostat + application. + properties: + coreSecurityContext: + description: Security Context to apply to the Cryostat application + container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a + process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag will + be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be set + when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the + container runtime. Note that this field cannot be set when + spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes in + privileged containers are essentially equivalent to root + on the host. Defaults to false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use + for the containers. The default is DefaultProcMount which + uses the container runtime defaults for readonly paths and + masked paths. This requires the ProcMountType feature flag + to be enabled. Note that this field cannot be set when spec.os.name + is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when + spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. Note + that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options from the PodSecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is + linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + dataSourceSecurityContext: + description: Security Context to apply to the JFR Data Source + container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a + process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag will + be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be set + when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the + container runtime. Note that this field cannot be set when + spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes in + privileged containers are essentially equivalent to root + on the host. Defaults to false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use + for the containers. The default is DefaultProcMount which + uses the container runtime defaults for readonly paths and + masked paths. This requires the ProcMountType feature flag + to be enabled. Note that this field cannot be set when spec.os.name + is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when + spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. Note + that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options from the PodSecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is + linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + grafanaSecurityContext: + description: Security Context to apply to the Grafana container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a + process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag will + be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be set + when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the + container runtime. Note that this field cannot be set when + spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes in + privileged containers are essentially equivalent to root + on the host. Defaults to false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use + for the containers. The default is DefaultProcMount which + uses the container runtime defaults for readonly paths and + masked paths. This requires the ProcMountType feature flag + to be enabled. Note that this field cannot be set when spec.os.name + is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when + spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. Note + that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options from the PodSecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is + linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + podSecurityContext: + description: Security Context to apply to the Cryostat pod. + properties: + fsGroup: + description: "A special supplemental group that applies to + all containers in a pod. Some volume types allow the Kubelet + to change the ownership of that volume to be owned by the + pod: \n 1. The owning GID will be the FSGroup 2. The setgid + bit is set (new files created in the volume will be owned + by FSGroup) 3. The permission bits are OR'd with rw-rw---- + \n If unset, the Kubelet will not modify the ownership and + permissions of any volume. Note that this field cannot be + set when spec.os.name is windows." + format: int64 + type: integer + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of changing + ownership and permission of the volume before being exposed + inside Pod. This field will only apply to volume types which + support fsGroup based ownership(and permissions). It will + have no effect on ephemeral volume types such as: secret, + configmaps and emptydir. Valid values are "OnRootMismatch" + and "Always". If not specified, "Always" is used. Note that + this field cannot be set when spec.os.name is windows.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this field + cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in SecurityContext. If set + in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence for that container. + Note that this field cannot be set when spec.os.name is + windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + SecurityContext. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by the containers + in this pod. Note that this field cannot be set when spec.os.name + is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's primary + GID. If unspecified, no groups will be added to any container. + Note that this field cannot be set when spec.os.name is + windows. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls used + for the pod. Pods with unsupported sysctls (by the container + runtime) might fail to launch. Note that this field cannot + be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options within a container's + SecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + type: object + serviceOptions: + description: Options to customize the services created for the Cryostat + application and Grafana dashboard. + properties: + coreConfig: + description: Specification for the service responsible for the + Cryostat application. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the service during its + creation. + type: object + httpPort: + description: HTTP port number for the Cryostat application + service. Defaults to 8181. + format: int32 + type: integer + jmxPort: + description: Remote JMX port number for the Cryostat application + service. Defaults to 9091. + format: int32 + type: integer + labels: + additionalProperties: + type: string + description: Labels to add to the service during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + serviceType: + description: Type of service to create. Defaults to "ClusterIP". + type: string + type: object + grafanaConfig: + description: Specification for the service responsible for the + Cryostat Grafana dashboard. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the service during its + creation. + type: object + httpPort: + description: HTTP port number for the Grafana dashboard service. + Defaults to 3000. + format: int32 + type: integer + labels: + additionalProperties: + type: string + description: Labels to add to the service during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + serviceType: + description: Type of service to create. Defaults to "ClusterIP". + type: string + type: object + reportsConfig: + description: Specification for the service responsible for the + cryostat-reports sidecars. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the service during its + creation. + type: object + httpPort: + description: HTTP port number for the cryostat-reports service. + Defaults to 10000. + format: int32 + type: integer + labels: + additionalProperties: + type: string + description: Labels to add to the service during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + serviceType: + description: Type of service to create. Defaults to "ClusterIP". + type: string + type: object + type: object + storageOptions: + description: Options to customize the storage for Flight Recordings + and Templates. + properties: + emptyDir: + description: Configuration for an EmptyDir to be created by the + operator instead of a PVC. + properties: + enabled: + description: When enabled, Cryostat will use EmptyDir volumes + instead of a Persistent Volume Claim. Any PVC configurations + will be ignored. + type: boolean + medium: + description: Unless specified, the emptyDir volume will be + mounted on the same storage medium backing the node. Setting + this field to "Memory" will mount the emptyDir on a tmpfs + (RAM-backed filesystem). + type: string + sizeLimit: + description: The maximum memory limit for the emptyDir. Default + is unbounded. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + type: string + type: object + pvc: + description: Configuration for the Persistent Volume Claim to + be created by the operator. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Persistent Volume Claim + during its creation. + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Persistent Volume Claim + during its creation. The label with key "app" is reserved + for use by the operator. + type: object + spec: + description: Spec for a Persistent Volume Claim, whose options + will override the defaults used by the operator. Unless + overriden, the PVC will be created with the default Storage + Class and 500MiB of storage. Once the operator has created + the PVC, changes to this field have no effect. + properties: + accessModes: + description: 'accessModes contains the desired access + modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'dataSource field can be used to specify + either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) If the provisioner + or an external controller can support the specified + data source, it will create a new volume based on the + contents of the specified data source. If the AnyVolumeDataSource + feature gate is enabled, this field will always have + the same contents as the DataSourceRef field.' + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'dataSourceRef specifies the object from + which to populate the volume with data, if a non-empty + volume is desired. This may be any local object from + a non-empty API group (non core object) or a PersistentVolumeClaim + object. When this field is specified, volume binding + will only succeed if the type of the specified object + matches some installed volume populator or dynamic provisioner. + This field will replace the functionality of the DataSource + field and as such if both fields are non-empty, they + must have the same value. For backwards compatibility, + both fields (DataSource and DataSourceRef) will be set + to the same value automatically if one of them is empty + and the other is non-empty. There are two important + differences between DataSource and DataSourceRef: * + While DataSource only allows two specific types of objects, + DataSourceRef allows any non-core object, as well as + PersistentVolumeClaim objects. * While DataSource ignores + disallowed values (dropping them), DataSourceRef preserves + all values, and generates an error if a disallowed value + is specified. (Beta) Using this field requires the AnyVolumeDataSource + feature gate to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'resources represents the minimum resources + the volume should have. If RecoverVolumeExpansionFailure + feature is enabled users are allowed to specify resource + requirements that are lower than previous value but + must still be higher than capacity recorded in the status + field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount + of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: selector is a label query over volumes to + consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values + array must be non-empty. If the operator is + Exists or DoesNotExist, the values array must + be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'storageClassName is the name of the StorageClass + required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is + required by the claim. Value of Filesystem is implied + when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference to the + PersistentVolume backing this claim. + type: string + type: object + type: object + type: object + targetDiscoveryOptions: + description: Options to configure the Cryostat application's target + discovery mechanisms. + properties: + builtInDiscoveryDisabled: + description: When true, the Cryostat application will disable + the built-in discovery mechanisms. Defaults to false + type: boolean + type: object + trustedCertSecrets: + description: List of TLS certificates to trust when connecting to + targets. + items: + properties: + certificateKey: + description: Key within secret containing the certificate. + type: string + secretName: + description: Name of secret in the local namespace. + type: string + required: + - secretName + type: object + type: array + required: + - minimal + type: object + status: + description: CryostatStatus defines the observed state of Cryostat. + properties: + applicationUrl: + description: Address of the deployed Cryostat web application. + type: string + conditions: + description: Conditions of the components managed by the Cryostat + Operator. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a foo's + current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + grafanaSecret: + description: Name of the Secret containing the generated Grafana credentials. + type: string + required: + - applicationUrl + type: object + type: object + served: true + storage: false diff --git a/internal/constants/constants.go b/internal/constants/constants.go new file mode 100644 index 000000000..c6a15e59c --- /dev/null +++ b/internal/constants/constants.go @@ -0,0 +1,56 @@ +// Copyright The Cryostat Authors +// +// The Universal Permissive License (UPL), Version 1.0 +// +// Subject to the condition set forth below, permission is hereby granted to any +// person obtaining a copy of this software, associated documentation and/or data +// (collectively the "Software"), free of charge and under any and all copyright +// rights in the Software, and any and all patent rights owned or freely +// licensable by each licensor hereunder covering either (i) the unmodified +// Software as contributed to or provided by such licensor, or (ii) the Larger +// Works (as defined below), to deal in both +// +// (a) the Software, and +// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +// one is included with the Software (each a "Larger Work" to which the Software +// is contributed by such licensors), +// +// without restriction, including without limitation the rights to copy, create +// derivative works of, display, perform, and distribute the Software and make, +// use, sell, offer for sale, import, export, have made, and have sold the +// Software and the Larger Work(s), and to sublicense the foregoing rights on +// either these or other terms. +// +// This license is subject to the following condition: +// The above copyright notice and either this complete permission notice or at +// a minimum a reference to the UPL must be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package constants + +import ( + certMeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" +) + +const ( + CryostatHTTPContainerPort int32 = 8181 + CryostatJMXContainerPort int32 = 9091 + GrafanaContainerPort int32 = 3000 + DatasourceContainerPort int32 = 8080 + ReportsContainerPort int32 = 10000 + LoopbackAddress string = "127.0.0.1" + OperatorNamePrefix string = "cryostat-operator-" + HttpPortName string = "http" + // CAKey is the key for a CA certificate within a TLS secret + CAKey = certMeta.TLSCAKey + // Hostname alias for loopback address, to be used for health checks + HealthCheckHostname = "cryostat-health.local" +) diff --git a/internal/controllers/certmanager.go b/internal/controllers/certmanager.go index b94f6d8c9..d8c348786 100644 --- a/internal/controllers/certmanager.go +++ b/internal/controllers/certmanager.go @@ -41,9 +41,9 @@ import ( "errors" "fmt" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "github.com/cryostatio/cryostat-operator/internal/controllers/common" resources "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" @@ -60,53 +60,53 @@ var errCertManagerMissing = errors.New("cert-manager integration is enabled, but const eventCertManagerUnavailableMsg = "cert-manager is not detected in the cluster, please install cert-manager or disable it by setting " + "\"enableCertManager\" in this Cryostat custom resource to false." -func (r *CryostatReconciler) setupTLS(ctx context.Context, cr *operatorv1beta1.Cryostat) (*resources.TLSConfig, error) { +func (r *Reconciler) setupTLS(ctx context.Context, cr *model.CryostatInstance) (*resources.TLSConfig, error) { // If cert-manager is not available, emit an Event to inform the user available, err := r.certManagerAvailable() if err != nil { return nil, err } if !available { - r.EventRecorder.Event(cr, corev1.EventTypeWarning, eventCertManagerUnavailableType, eventCertManagerUnavailableMsg) + r.EventRecorder.Event(cr.Instance, corev1.EventTypeWarning, eventCertManagerUnavailableType, eventCertManagerUnavailableMsg) return nil, errCertManagerMissing } // Create self-signed issuer used to bootstrap CA - err = r.createOrUpdateIssuer(ctx, resources.NewSelfSignedIssuer(cr), cr) + err = r.createOrUpdateIssuer(ctx, resources.NewSelfSignedIssuer(cr), cr.Instance) if err != nil { return nil, err } // Create CA certificate for Cryostat using the self-signed issuer caCert := resources.NewCryostatCACert(cr) - err = r.createOrUpdateCertificate(ctx, caCert, cr) + err = r.createOrUpdateCertificate(ctx, caCert, cr.Instance) if err != nil { return nil, err } // Create CA issuer using the CA cert just created - err = r.createOrUpdateIssuer(ctx, resources.NewCryostatCAIssuer(cr), cr) + err = r.createOrUpdateIssuer(ctx, resources.NewCryostatCAIssuer(cr), cr.Instance) if err != nil { return nil, err } // Create secret to hold keystore password keystoreSecret := newKeystoreSecret(cr) - err = r.createOrUpdateKeystoreSecret(ctx, keystoreSecret, cr) + err = r.createOrUpdateKeystoreSecret(ctx, keystoreSecret, cr.Instance) if err != nil { return nil, err } // Create a certificate for Cryostat signed by the CA just created cryostatCert := resources.NewCryostatCert(cr, keystoreSecret.Name) - err = r.createOrUpdateCertificate(ctx, cryostatCert, cr) + err = r.createOrUpdateCertificate(ctx, cryostatCert, cr.Instance) if err != nil { return nil, err } // Create a certificate for the reports generator signed by the Cryostat CA reportsCert := resources.NewReportsCert(cr) - err = r.createOrUpdateCertificate(ctx, reportsCert, cr) + err = r.createOrUpdateCertificate(ctx, reportsCert, cr.Instance) if err != nil { return nil, err } @@ -119,7 +119,7 @@ func (r *CryostatReconciler) setupTLS(ctx context.Context, cr *operatorv1beta1.C // Create a certificate for Grafana signed by the Cryostat CA if !cr.Spec.Minimal { grafanaCert := resources.NewGrafanaCert(cr) - err = r.createOrUpdateCertificate(ctx, grafanaCert, cr) + err = r.createOrUpdateCertificate(ctx, grafanaCert, cr.Instance) if err != nil { return nil, err } @@ -139,7 +139,7 @@ func (r *CryostatReconciler) setupTLS(ctx context.Context, cr *operatorv1beta1.C } // Update owner references of TLS secrets created by cert-manager to ensure proper cleanup - err = r.setCertSecretOwner(ctx, cr, certificates...) + err = r.setCertSecretOwner(ctx, cr.Instance, certificates...) if err != nil { return nil, err } @@ -153,7 +153,7 @@ func (r *CryostatReconciler) setupTLS(ctx context.Context, cr *operatorv1beta1.C return tlsConfig, nil } -func (r *CryostatReconciler) setCertSecretOwner(ctx context.Context, cr *operatorv1beta1.Cryostat, certs ...*certv1.Certificate) error { +func (r *Reconciler) setCertSecretOwner(ctx context.Context, owner metav1.Object, certs ...*certv1.Certificate) error { // Make Cryostat CR controller of secrets created by cert-manager for _, cert := range certs { secret, err := r.GetCertificateSecret(ctx, cert) @@ -163,8 +163,8 @@ func (r *CryostatReconciler) setCertSecretOwner(ctx context.Context, cr *operato } return err } - if !metav1.IsControlledBy(secret, cr) { - err = controllerutil.SetControllerReference(cr, secret, r.Scheme) + if !metav1.IsControlledBy(secret, owner) { + err = controllerutil.SetControllerReference(owner, secret, r.Scheme) if err != nil { return err } @@ -187,7 +187,7 @@ func secretForCertificate(cert *certv1.Certificate) *corev1.Secret { } } -func (r *CryostatReconciler) certManagerAvailable() (bool, error) { +func (r *Reconciler) certManagerAvailable() (bool, error) { // Check if cert-manager API is available. Checking just one should be enough. _, err := r.RESTMapper.RESTMapping(schema.GroupKind{ Group: certv1.SchemeGroupVersion.Group, @@ -204,7 +204,7 @@ func (r *CryostatReconciler) certManagerAvailable() (bool, error) { return true, nil } -func (r *CryostatReconciler) createOrUpdateIssuer(ctx context.Context, issuer *certv1.Issuer, owner metav1.Object) error { +func (r *Reconciler) createOrUpdateIssuer(ctx context.Context, issuer *certv1.Issuer, owner metav1.Object) error { issuerSpec := issuer.Spec.DeepCopy() op, err := controllerutil.CreateOrUpdate(ctx, r.Client, issuer, func() error { if err := controllerutil.SetControllerReference(owner, issuer, r.Scheme); err != nil { @@ -221,7 +221,7 @@ func (r *CryostatReconciler) createOrUpdateIssuer(ctx context.Context, issuer *c return nil } -func (r *CryostatReconciler) createOrUpdateCertificate(ctx context.Context, cert *certv1.Certificate, owner metav1.Object) error { +func (r *Reconciler) createOrUpdateCertificate(ctx context.Context, cert *certv1.Certificate, owner metav1.Object) error { certSpec := cert.Spec.DeepCopy() op, err := controllerutil.CreateOrUpdate(ctx, r.Client, cert, func() error { if err := controllerutil.SetControllerReference(owner, cert, r.Scheme); err != nil { @@ -238,16 +238,16 @@ func (r *CryostatReconciler) createOrUpdateCertificate(ctx context.Context, cert return nil } -func newKeystoreSecret(cr *operatorv1beta1.Cryostat) *corev1.Secret { +func newKeystoreSecret(cr *model.CryostatInstance) *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-keystore", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } } -func (r *CryostatReconciler) createOrUpdateKeystoreSecret(ctx context.Context, secret *corev1.Secret, owner metav1.Object) error { +func (r *Reconciler) createOrUpdateKeystoreSecret(ctx context.Context, secret *corev1.Secret, owner metav1.Object) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, secret, func() error { if err := controllerutil.SetControllerReference(owner, secret, r.Scheme); err != nil { return err @@ -268,7 +268,7 @@ func (r *CryostatReconciler) createOrUpdateKeystoreSecret(ctx context.Context, s return nil } -func (r *CryostatReconciler) deleteCert(ctx context.Context, cert *certv1.Certificate) error { +func (r *Reconciler) deleteCert(ctx context.Context, cert *certv1.Certificate) error { err := r.Client.Delete(ctx, cert) if err != nil && !kerrors.IsNotFound(err) { r.Log.Error(err, "Could not delete certificate", "name", cert.Name, "namespace", cert.Namespace) @@ -278,7 +278,7 @@ func (r *CryostatReconciler) deleteCert(ctx context.Context, cert *certv1.Certif return nil } -func (r *CryostatReconciler) getCertficateBytes(ctx context.Context, cert *certv1.Certificate) ([]byte, error) { +func (r *Reconciler) getCertficateBytes(ctx context.Context, cert *certv1.Certificate) ([]byte, error) { secret, err := r.GetCertificateSecret(ctx, cert) if err != nil { return nil, err diff --git a/internal/controllers/cluster_cryostat_controller.go b/internal/controllers/cluster_cryostat_controller.go new file mode 100644 index 000000000..54104c337 --- /dev/null +++ b/internal/controllers/cluster_cryostat_controller.go @@ -0,0 +1,142 @@ +// Copyright The Cryostat Authors +// +// The Universal Permissive License (UPL), Version 1.0 +// +// Subject to the condition set forth below, permission is hereby granted to any +// person obtaining a copy of this software, associated documentation and/or data +// (collectively the "Software"), free of charge and under any and all copyright +// rights in the Software, and any and all patent rights owned or freely +// licensable by each licensor hereunder covering either (i) the unmodified +// Software as contributed to or provided by such licensor, or (ii) the Larger +// Works (as defined below), to deal in both +// +// (a) the Software, and +// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +// one is included with the Software (each a "Larger Work" to which the Software +// is contributed by such licensors), +// +// without restriction, including without limitation the rights to copy, create +// derivative works of, display, perform, and distribute the Software and make, +// use, sell, offer for sale, import, export, have made, and have sold the +// Software and the Larger Work(s), and to sublicense the foregoing rights on +// either these or other terms. +// +// This license is subject to the following condition: +// The above copyright notice and either this complete permission notice or at +// a minimum a reference to the UPL must be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package controllers + +import ( + "context" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" + + certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" + openshiftv1 "github.com/openshift/api/route/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + netv1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// Generates constants from environment variables at build time +//go:generate go run ../tools/const_generator.go + +// CryostatReconciler reconciles a Cryostat object +type ClusterCryostatReconciler struct { + delegate *Reconciler + *ReconcilerConfig +} + +func NewClusterCryostatReconciler(config *ReconcilerConfig) *ClusterCryostatReconciler { + return &ClusterCryostatReconciler{ + ReconcilerConfig: config, + delegate: &Reconciler{ + ReconcilerConfig: config, + }, + } +} + +// +kubebuilder:rbac:namespace=system,groups="",resources=pods;services;services/finalizers;endpoints;persistentvolumeclaims;events;configmaps;secrets;serviceaccounts,verbs=* +// +kubebuilder:rbac:namespace=system,groups="",resources=replicationcontrollers,verbs=get +// +kubebuilder:rbac:namespace=system,groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=create;get;list;update;watch;delete +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=create;get;list;update;watch;delete +// +kubebuilder:rbac:groups=authentication.k8s.io,resources=tokenreviews,verbs=create +// +kubebuilder:rbac:groups=authorization.k8s.io,resources=selfsubjectaccessreviews,verbs=create +// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch +// +kubebuilder:rbac:groups=oauth.openshift.io,resources=oauthaccesstokens,verbs=list;delete +// +kubebuilder:rbac:groups=config.openshift.io,resources=apiservers,verbs=get;list;update;watch +// +kubebuilder:rbac:namespace=system,groups=route.openshift.io,resources=routes;routes/custom-host,verbs=* +// +kubebuilder:rbac:namespace=system,groups=apps.openshift.io,resources=deploymentconfigs,verbs=get +// +kubebuilder:rbac:namespace=system,groups=apps,resources=deployments;daemonsets;replicasets;statefulsets,verbs=* +// +kubebuilder:rbac:namespace=system,groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;create +// +kubebuilder:rbac:namespace=system,groups=cert-manager.io,resources=issuers;certificates,verbs=create;get;list;update;watch;delete +// +kubebuilder:rbac:groups=operator.cryostat.io,resources=clustercryostats,verbs=* +// +kubebuilder:rbac:groups=operator.cryostat.io,resources=clustercryostats/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=operator.cryostat.io,resources=clustercryostats/finalizers,verbs=update +// +kubebuilder:rbac:groups=console.openshift.io,resources=consolelinks,verbs=get;create;list;update;delete +// +kubebuilder:rbac:namespace=system,groups=networking.k8s.io,resources=ingresses,verbs=* + +// Reconcile processes a ClusterCryostat CR and manages a Cryostat installation accordingly +func (r *ClusterCryostatReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { + reqLogger := r.Log.WithValues("Request.Name", request.Name) + + reqLogger.Info("Reconciling ClusterCryostat") + + // Fetch the Cryostat instance + cr := &operatorv1beta1.ClusterCryostat{} + err := r.Client.Get(ctx, request.NamespacedName, cr) + if err != nil { + if kerrors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + reqLogger.Info("ClusterCryostat instance not found") + return reconcile.Result{}, nil + } + reqLogger.Error(err, "Error reading ClusterCryostat instance") + return reconcile.Result{}, err + } + + instance := model.FromClusterCryostat(cr) + return r.delegate.ReconcileCryostat(ctx, instance) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ClusterCryostatReconciler) SetupWithManager(mgr ctrl.Manager) error { + c := ctrl.NewControllerManagedBy(mgr). + For(&operatorv1beta1.ClusterCryostat{}) + + // Watch for changes to secondary resources and requeue the owner Cryostat + resources := []client.Object{&appsv1.Deployment{}, &corev1.Service{}, &corev1.Secret{}, &corev1.PersistentVolumeClaim{}, + &corev1.ServiceAccount{}, &rbacv1.Role{}, &rbacv1.RoleBinding{}, &netv1.Ingress{}} + if r.IsOpenShift { + resources = append(resources, &openshiftv1.Route{}) + } + // Can only check this at startup + if r.IsCertManagerInstalled { + resources = append(resources, &certv1.Issuer{}, &certv1.Certificate{}) + } + + for _, resource := range resources { + c = c.Owns(resource) + } + + return c.Complete(r) +} diff --git a/internal/controllers/common/common_utils.go b/internal/controllers/common/common_utils.go index 2c6f78dc5..841e77b67 100644 --- a/internal/controllers/common/common_utils.go +++ b/internal/controllers/common/common_utils.go @@ -44,7 +44,6 @@ import ( "os" "time" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "k8s.io/apimachinery/pkg/types" logf "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -83,10 +82,10 @@ func (o *defaultOSUtils) GenPasswd(length int) string { } // ClusterUniqueName returns a name for cluster-scoped objects that is -// uniquely identified by the namespace and name of the provided Cryostat CR. -func ClusterUniqueName(cr *operatorv1beta1.Cryostat) string { +// uniquely identified by a namespace and name. +func ClusterUniqueName(name string, namespace string) string { // Use the SHA256 checksum of the namespaced name as a suffix - nn := types.NamespacedName{Namespace: cr.Namespace, Name: cr.Name} + nn := types.NamespacedName{Namespace: namespace, Name: name} suffix := fmt.Sprintf("%x", sha256.Sum256([]byte(nn.String()))) return "cryostat-" + suffix } diff --git a/internal/controllers/common/resource_definitions/certificates.go b/internal/controllers/common/resource_definitions/certificates.go index 0a8749a54..bff758799 100644 --- a/internal/controllers/common/resource_definitions/certificates.go +++ b/internal/controllers/common/resource_definitions/certificates.go @@ -39,20 +39,18 @@ package resource_definitions import ( "fmt" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + "github.com/cryostatio/cryostat-operator/internal/constants" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" certMeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// CAKey is the key for a CA certificate within a TLS secret -const CAKey = certMeta.TLSCAKey - -func NewSelfSignedIssuer(cr *operatorv1beta1.Cryostat) *certv1.Issuer { +func NewSelfSignedIssuer(cr *model.CryostatInstance) *certv1.Issuer { return &certv1.Issuer{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-self-signed", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, Spec: certv1.IssuerSpec{ IssuerConfig: certv1.IssuerConfig{ @@ -62,11 +60,11 @@ func NewSelfSignedIssuer(cr *operatorv1beta1.Cryostat) *certv1.Issuer { } } -func NewCryostatCAIssuer(cr *operatorv1beta1.Cryostat) *certv1.Issuer { +func NewCryostatCAIssuer(cr *model.CryostatInstance) *certv1.Issuer { return &certv1.Issuer{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-ca", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, Spec: certv1.IssuerSpec{ IssuerConfig: certv1.IssuerConfig{ @@ -78,11 +76,11 @@ func NewCryostatCAIssuer(cr *operatorv1beta1.Cryostat) *certv1.Issuer { } } -func NewCryostatCACert(cr *operatorv1beta1.Cryostat) *certv1.Certificate { +func NewCryostatCACert(cr *model.CryostatInstance) *certv1.Certificate { return &certv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-ca", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, Spec: certv1.CertificateSpec{ CommonName: fmt.Sprintf("ca.%s.cert-manager", cr.Name), @@ -95,18 +93,18 @@ func NewCryostatCACert(cr *operatorv1beta1.Cryostat) *certv1.Certificate { } } -func NewCryostatCert(cr *operatorv1beta1.Cryostat, keystoreSecretName string) *certv1.Certificate { +func NewCryostatCert(cr *model.CryostatInstance, keystoreSecretName string) *certv1.Certificate { return &certv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, Spec: certv1.CertificateSpec{ - CommonName: fmt.Sprintf("%s.%s.svc", cr.Name, cr.Namespace), + CommonName: fmt.Sprintf("%s.%s.svc", cr.Name, cr.InstallNamespace), DNSNames: []string{ cr.Name, - fmt.Sprintf("%s.%s.svc", cr.Name, cr.Namespace), - fmt.Sprintf("%s.%s.svc.cluster.local", cr.Name, cr.Namespace), + fmt.Sprintf("%s.%s.svc", cr.Name, cr.InstallNamespace), + fmt.Sprintf("%s.%s.svc.cluster.local", cr.Name, cr.InstallNamespace), }, SecretName: cr.Name + "-tls", Keystores: &certv1.CertificateKeystores{ @@ -131,19 +129,19 @@ func NewCryostatCert(cr *operatorv1beta1.Cryostat, keystoreSecretName string) *c } } -func NewGrafanaCert(cr *operatorv1beta1.Cryostat) *certv1.Certificate { +func NewGrafanaCert(cr *model.CryostatInstance) *certv1.Certificate { return &certv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-grafana", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, Spec: certv1.CertificateSpec{ - CommonName: fmt.Sprintf("%s-grafana.%s.svc", cr.Name, cr.Namespace), + CommonName: fmt.Sprintf("%s-grafana.%s.svc", cr.Name, cr.InstallNamespace), DNSNames: []string{ cr.Name + "-grafana", - fmt.Sprintf("%s-grafana.%s.svc", cr.Name, cr.Namespace), - fmt.Sprintf("%s-grafana.%s.svc.cluster.local", cr.Name, cr.Namespace), - healthCheckHostname, + fmt.Sprintf("%s-grafana.%s.svc", cr.Name, cr.InstallNamespace), + fmt.Sprintf("%s-grafana.%s.svc.cluster.local", cr.Name, cr.InstallNamespace), + constants.HealthCheckHostname, }, SecretName: cr.Name + "-grafana-tls", IssuerRef: certMeta.ObjectReference{ @@ -156,18 +154,18 @@ func NewGrafanaCert(cr *operatorv1beta1.Cryostat) *certv1.Certificate { } } -func NewReportsCert(cr *operatorv1beta1.Cryostat) *certv1.Certificate { +func NewReportsCert(cr *model.CryostatInstance) *certv1.Certificate { return &certv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-reports", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, Spec: certv1.CertificateSpec{ - CommonName: fmt.Sprintf("%s-reports.%s.svc", cr.Name, cr.Namespace), + CommonName: fmt.Sprintf("%s-reports.%s.svc", cr.Name, cr.InstallNamespace), DNSNames: []string{ cr.Name + "-reports", - fmt.Sprintf("%s-reports.%s.svc", cr.Name, cr.Namespace), - fmt.Sprintf("%s-reports.%s.svc.cluster.local", cr.Name, cr.Namespace), + fmt.Sprintf("%s-reports.%s.svc", cr.Name, cr.InstallNamespace), + fmt.Sprintf("%s-reports.%s.svc.cluster.local", cr.Name, cr.InstallNamespace), }, SecretName: cr.Name + "-reports-tls", IssuerRef: certMeta.ObjectReference{ diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 5a4e5e0e0..64393d577 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -43,6 +43,8 @@ import ( "strconv" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + "github.com/cryostatio/cryostat-operator/internal/constants" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -89,24 +91,14 @@ const ( defaultReportMemoryRequest string = "256Mi" ) -const ( - cryostatHTTPContainerPort int32 = 8181 - cryostatJMXContainerPort int32 = 9091 - grafanaContainerPort int32 = 3000 - datasourceContainerPort int32 = 8080 - reportsContainerPort int32 = 10000 - loopbackAddress string = "127.0.0.1" - operatorNamePrefix string = "cryostat-operator-" -) - -func NewDeploymentForCR(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTags *ImageTags, +func NewDeploymentForCR(cr *model.CryostatInstance, specs *ServiceSpecs, imageTags *ImageTags, tls *TLSConfig, fsGroup int64, openshift bool) *appsv1.Deployment { // Force one replica to avoid lock file and PVC contention replicas := int32(1) return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, Labels: map[string]string{ "app": cr.Name, "kind": "cryostat", @@ -129,7 +121,7 @@ func NewDeploymentForCR(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, image Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, Labels: map[string]string{ "app": cr.Name, "kind": "cryostat", @@ -146,7 +138,7 @@ func NewDeploymentForCR(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, image } } -func NewDeploymentForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags, tls *TLSConfig, +func NewDeploymentForReports(cr *model.CryostatInstance, imageTags *ImageTags, tls *TLSConfig, openshift bool) *appsv1.Deployment { replicas := int32(0) if cr.Spec.ReportOptions != nil { @@ -155,7 +147,7 @@ func NewDeploymentForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags, return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-reports", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, Labels: map[string]string{ "app": cr.Name, "kind": "cryostat", @@ -178,7 +170,7 @@ func NewDeploymentForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-reports", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, Labels: map[string]string{ "app": cr.Name, "kind": "cryostat", @@ -192,10 +184,7 @@ func NewDeploymentForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags, } } -// Hostname alias for loopback address, to be used for health checks -const healthCheckHostname = "cryostat-health.local" - -func NewPodForCR(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTags *ImageTags, +func NewPodForCR(cr *model.CryostatInstance, specs *ServiceSpecs, imageTags *ImageTags, tls *TLSConfig, fsGroup int64, openshift bool) *corev1.PodSpec { var containers []corev1.Container if cr.Spec.Minimal { @@ -248,7 +237,7 @@ func NewPodForCR(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTags *I }, Items: []corev1.KeyToPath{ { - Key: CAKey, + Key: constants.CAKey, Path: cr.Name + "-ca.crt", Mode: &readOnlyMode, }, @@ -359,9 +348,9 @@ func NewPodForCR(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTags *I // work over HTTPS with hostname added as a SubjectAltName hostAliases := []corev1.HostAlias{ { - IP: loopbackAddress, + IP: constants.LoopbackAddress, Hostnames: []string{ - healthCheckHostname, + constants.HealthCheckHostname, }, }, } @@ -396,7 +385,7 @@ func NewPodForCR(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTags *I } } -func NewReportContainerResource(cr *operatorv1beta1.Cryostat) *corev1.ResourceRequirements { +func NewReportContainerResource(cr *model.CryostatInstance) *corev1.ResourceRequirements { resources := &corev1.ResourceRequirements{} if cr.Spec.ReportOptions != nil { resources = cr.Spec.ReportOptions.Resources.DeepCopy() @@ -409,7 +398,7 @@ func NewReportContainerResource(cr *operatorv1beta1.Cryostat) *corev1.ResourceRe // https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted const capabilityAll corev1.Capability = "ALL" -func NewPodForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags, tls *TLSConfig, openshift bool) *corev1.PodSpec { +func NewPodForReports(cr *model.CryostatInstance, imageTags *ImageTags, tls *TLSConfig, openshift bool) *corev1.PodSpec { resources := NewReportContainerResource(cr) cpus := resources.Requests.Cpu().Value() // Round to 1 if cpu request < 1000m if limits := resources.Limits; limits != nil { @@ -438,7 +427,7 @@ func NewPodForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags, tls *T tlsEnvs := []corev1.EnvVar{ { Name: "QUARKUS_HTTP_SSL_PORT", - Value: strconv.Itoa(int(reportsContainerPort)), + Value: strconv.Itoa(int(constants.ReportsContainerPort)), }, { Name: "QUARKUS_HTTP_SSL_CERTIFICATE_KEY_FILE", @@ -479,14 +468,14 @@ func NewPodForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags, tls *T } else { envs = append(envs, corev1.EnvVar{ Name: "QUARKUS_HTTP_PORT", - Value: strconv.Itoa(int(reportsContainerPort)), + Value: strconv.Itoa(int(constants.ReportsContainerPort)), }) } probeHandler := corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ Scheme: livenessProbeScheme, - Port: intstr.IntOrString{IntVal: reportsContainerPort}, + Port: intstr.IntOrString{IntVal: constants.ReportsContainerPort}, Path: "/health", }, } @@ -540,7 +529,7 @@ func NewPodForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags, tls *T ImagePullPolicy: getPullPolicy(imageTags.ReportsImageTag), Ports: []corev1.ContainerPort{ { - ContainerPort: reportsContainerPort, + ContainerPort: constants.ReportsContainerPort, }, }, Env: envs, @@ -563,7 +552,7 @@ func NewPodForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags, tls *T } } -func NewCoreContainerResource(cr *operatorv1beta1.Cryostat) *corev1.ResourceRequirements { +func NewCoreContainerResource(cr *model.CryostatInstance) *corev1.ResourceRequirements { resources := &corev1.ResourceRequirements{} if cr.Spec.Resources != nil { resources = cr.Spec.Resources.CoreResources.DeepCopy() @@ -572,7 +561,7 @@ func NewCoreContainerResource(cr *operatorv1beta1.Cryostat) *corev1.ResourceRequ return resources } -func NewCoreContainer(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTag string, +func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag string, tls *TLSConfig, openshift bool) corev1.Container { configPath := "/opt/cryostat.d/conf.d" archivePath := "/opt/cryostat.d/recordings.d" @@ -584,7 +573,7 @@ func NewCoreContainer(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTa envs := []corev1.EnvVar{ { Name: "CRYOSTAT_WEB_PORT", - Value: strconv.Itoa(int(cryostatHTTPContainerPort)), + Value: strconv.Itoa(int(constants.CryostatHTTPContainerPort)), }, { Name: "CRYOSTAT_CONFIG_PATH", @@ -747,7 +736,7 @@ func NewCoreContainer(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTa }, { Name: "CRYOSTAT_BASE_OAUTH_ROLE", - Value: operatorNamePrefix + "oauth-client", + Value: constants.OperatorNamePrefix + "oauth-client", }, } envs = append(envs, openshiftEnvs...) @@ -890,7 +879,7 @@ func NewCoreContainer(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTa probeHandler := corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.IntOrString{IntVal: cryostatHTTPContainerPort}, + Port: intstr.IntOrString{IntVal: constants.CryostatHTTPContainerPort}, Path: "/health/liveness", Scheme: livenessProbeScheme, }, @@ -916,10 +905,10 @@ func NewCoreContainer(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTa VolumeMounts: mounts, Ports: []corev1.ContainerPort{ { - ContainerPort: cryostatHTTPContainerPort, + ContainerPort: constants.CryostatHTTPContainerPort, }, { - ContainerPort: cryostatJMXContainerPort, + ContainerPort: constants.CryostatJMXContainerPort, }, }, Env: envs, @@ -937,7 +926,7 @@ func NewCoreContainer(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTa } } -func NewGrafanaContainerResource(cr *operatorv1beta1.Cryostat) *corev1.ResourceRequirements { +func NewGrafanaContainerResource(cr *model.CryostatInstance) *corev1.ResourceRequirements { resources := &corev1.ResourceRequirements{} if cr.Spec.Resources != nil { resources = cr.Spec.Resources.GrafanaResources.DeepCopy() @@ -946,7 +935,7 @@ func NewGrafanaContainerResource(cr *operatorv1beta1.Cryostat) *corev1.ResourceR return resources } -func NewGrafanaContainer(cr *operatorv1beta1.Cryostat, imageTag string, tls *TLSConfig) corev1.Container { +func NewGrafanaContainer(cr *model.CryostatInstance, imageTag string, tls *TLSConfig) corev1.Container { envs := []corev1.EnvVar{ { Name: "JFR_DATASOURCE_URL", @@ -1006,7 +995,7 @@ func NewGrafanaContainer(cr *operatorv1beta1.Cryostat, imageTag string, tls *TLS VolumeMounts: mounts, Ports: []corev1.ContainerPort{ { - ContainerPort: grafanaContainerPort, + ContainerPort: constants.GrafanaContainerPort, }, }, Env: envs, @@ -1034,9 +1023,9 @@ func NewGrafanaContainer(cr *operatorv1beta1.Cryostat, imageTag string, tls *TLS } // datasourceURL contains the fixed URL to jfr-datasource's web server -var datasourceURL = "http://" + loopbackAddress + ":" + strconv.Itoa(int(datasourceContainerPort)) +var datasourceURL = "http://" + constants.LoopbackAddress + ":" + strconv.Itoa(int(constants.DatasourceContainerPort)) -func NewJfrDatasourceContainerResource(cr *operatorv1beta1.Cryostat) *corev1.ResourceRequirements { +func NewJfrDatasourceContainerResource(cr *model.CryostatInstance) *corev1.ResourceRequirements { resources := &corev1.ResourceRequirements{} if cr.Spec.Resources != nil { resources = cr.Spec.Resources.DataSourceResources.DeepCopy() @@ -1045,7 +1034,7 @@ func NewJfrDatasourceContainerResource(cr *operatorv1beta1.Cryostat) *corev1.Res return resources } -func NewJfrDatasourceContainer(cr *operatorv1beta1.Cryostat, imageTag string) corev1.Container { +func NewJfrDatasourceContainer(cr *model.CryostatInstance, imageTag string) corev1.Container { var containerSc *corev1.SecurityContext if cr.Spec.SecurityOptions != nil && cr.Spec.SecurityOptions.DataSourceSecurityContext != nil { containerSc = cr.Spec.SecurityOptions.DataSourceSecurityContext @@ -1065,13 +1054,13 @@ func NewJfrDatasourceContainer(cr *operatorv1beta1.Cryostat, imageTag string) co ImagePullPolicy: getPullPolicy(imageTag), Ports: []corev1.ContainerPort{ { - ContainerPort: datasourceContainerPort, + ContainerPort: constants.DatasourceContainerPort, }, }, Env: []corev1.EnvVar{ { Name: "LISTEN_HOST", - Value: loopbackAddress, + Value: constants.LoopbackAddress, }, }, // Can't use HTTP probe since the port is not exposed over the network @@ -1105,7 +1094,7 @@ func getInternalDashboardURL(tls *TLSConfig) string { if tls == nil { scheme = "http" } - return fmt.Sprintf("%s://%s:%d", scheme, healthCheckHostname, grafanaContainerPort) + return fmt.Sprintf("%s://%s:%d", scheme, constants.HealthCheckHostname, constants.GrafanaContainerPort) } // Matches image tags of the form "major.minor.patch" @@ -1120,7 +1109,7 @@ func getPullPolicy(imageTag string) corev1.PullPolicy { return corev1.PullIfNotPresent } -func newVolumeForCR(cr *operatorv1beta1.Cryostat) []corev1.Volume { +func newVolumeForCR(cr *model.CryostatInstance) []corev1.Volume { var volumeSource corev1.VolumeSource if useEmptyDir(cr) { emptyDir := cr.Spec.StorageOptions.EmptyDir @@ -1164,7 +1153,7 @@ func seccompProfile(openshift bool) *corev1.SeccompProfile { } } -func useEmptyDir(cr *operatorv1beta1.Cryostat) bool { +func useEmptyDir(cr *model.CryostatInstance) bool { return cr.Spec.StorageOptions != nil && cr.Spec.StorageOptions.EmptyDir != nil && cr.Spec.StorageOptions.EmptyDir.Enabled } diff --git a/internal/controllers/common/tls.go b/internal/controllers/common/tls.go index 7de089e0e..6e0cdc7eb 100644 --- a/internal/controllers/common/tls.go +++ b/internal/controllers/common/tls.go @@ -41,7 +41,7 @@ import ( "errors" "strings" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" certMeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" corev1 "k8s.io/api/core/v1" @@ -52,7 +52,7 @@ import ( // ReconcilerTLS contains methods a reconciler may wish to use when configuring // TLS-related functionality type ReconcilerTLS interface { - IsCertManagerEnabled(cryostat *operatorv1beta1.Cryostat) bool + IsCertManagerEnabled(cr *model.CryostatInstance) bool GetCertificateSecret(ctx context.Context, cert *certv1.Certificate) (*corev1.Secret, error) OSUtils } @@ -90,7 +90,7 @@ func NewReconcilerTLS(config *ReconcilerTLSConfig) ReconcilerTLS { // IsCertManagerEnabled returns whether TLS using cert-manager is enabled // for this operator -func (r *reconcilerTLS) IsCertManagerEnabled(cr *operatorv1beta1.Cryostat) bool { +func (r *reconcilerTLS) IsCertManagerEnabled(cr *model.CryostatInstance) bool { // First check if cert-manager is explicitly enabled or disabled in CR if cr.Spec.EnableCertManager != nil { return *cr.Spec.EnableCertManager diff --git a/internal/controllers/ingresses.go b/internal/controllers/ingresses.go index 2f2b93eba..2688cb032 100644 --- a/internal/controllers/ingresses.go +++ b/internal/controllers/ingresses.go @@ -41,20 +41,22 @@ import ( "fmt" "net/url" + //operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" netv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func (r *CryostatReconciler) reconcileCoreIngress(ctx context.Context, cr *operatorv1beta1.Cryostat, +func (r *Reconciler) reconcileCoreIngress(ctx context.Context, cr *model.CryostatInstance, specs *resource_definitions.ServiceSpecs) error { ingress := &netv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } @@ -72,12 +74,12 @@ func (r *CryostatReconciler) reconcileCoreIngress(ctx context.Context, cr *opera return nil } -func (r *CryostatReconciler) reconcileGrafanaIngress(ctx context.Context, cr *operatorv1beta1.Cryostat, +func (r *Reconciler) reconcileGrafanaIngress(ctx context.Context, cr *model.CryostatInstance, specs *resource_definitions.ServiceSpecs) error { ingress := &netv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-grafana", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } @@ -95,9 +97,9 @@ func (r *CryostatReconciler) reconcileGrafanaIngress(ctx context.Context, cr *op return nil } -func (r *CryostatReconciler) reconcileIngress(ctx context.Context, ingress *netv1.Ingress, cr *operatorv1beta1.Cryostat, +func (r *Reconciler) reconcileIngress(ctx context.Context, ingress *netv1.Ingress, cr *model.CryostatInstance, config *operatorv1beta1.NetworkConfiguration) (*url.URL, error) { - ingress, err := r.createOrUpdateIngress(ctx, ingress, cr, config) + ingress, err := r.createOrUpdateIngress(ctx, ingress, cr.Instance, config) if err != nil { return nil, err } @@ -117,7 +119,7 @@ func (r *CryostatReconciler) reconcileIngress(ctx context.Context, ingress *netv }, nil } -func (r *CryostatReconciler) createOrUpdateIngress(ctx context.Context, ingress *netv1.Ingress, owner metav1.Object, +func (r *Reconciler) createOrUpdateIngress(ctx context.Context, ingress *netv1.Ingress, owner metav1.Object, config *operatorv1beta1.NetworkConfiguration) (*netv1.Ingress, error) { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, ingress, func() error { // Set labels and annotations from CR @@ -138,7 +140,7 @@ func (r *CryostatReconciler) createOrUpdateIngress(ctx context.Context, ingress return ingress, nil } -func (r *CryostatReconciler) deleteIngress(ctx context.Context, ingress *netv1.Ingress) error { +func (r *Reconciler) deleteIngress(ctx context.Context, ingress *netv1.Ingress) error { err := r.Client.Delete(ctx, ingress) if err != nil && !errors.IsNotFound(err) { r.Log.Error(err, "Could not delete ingress", "name", ingress.Name, "namespace", ingress.Namespace) diff --git a/internal/controllers/model/instance.go b/internal/controllers/model/instance.go new file mode 100644 index 000000000..700c548cf --- /dev/null +++ b/internal/controllers/model/instance.go @@ -0,0 +1,79 @@ +// Copyright The Cryostat Authors +// +// The Universal Permissive License (UPL), Version 1.0 +// +// Subject to the condition set forth below, permission is hereby granted to any +// person obtaining a copy of this software, associated documentation and/or data +// (collectively the "Software"), free of charge and under any and all copyright +// rights in the Software, and any and all patent rights owned or freely +// licensable by each licensor hereunder covering either (i) the unmodified +// Software as contributed to or provided by such licensor, or (ii) the Larger +// Works (as defined below), to deal in both +// +// (a) the Software, and +// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +// one is included with the Software (each a "Larger Work" to which the Software +// is contributed by such licensors), +// +// without restriction, including without limitation the rights to copy, create +// derivative works of, display, perform, and distribute the Software and make, +// use, sell, offer for sale, import, export, have made, and have sold the +// Software and the Larger Work(s), and to sublicense the foregoing rights on +// either these or other terms. +// +// This license is subject to the following condition: +// The above copyright notice and either this complete permission notice or at +// a minimum a reference to the UPL must be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package model + +import ( + operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type CryostatInstance struct { + Name string + InstallNamespace string + TargetNamespaces []string + + Spec *operatorv1beta1.CryostatSpec + Status *operatorv1beta1.CryostatStatus + + Instance client.Object +} + +func FromCryostat(cr *operatorv1beta1.Cryostat) *CryostatInstance { + return &CryostatInstance{ + Name: cr.Name, + InstallNamespace: cr.Namespace, + TargetNamespaces: []string{cr.Namespace}, + + Spec: &cr.Spec, + Status: &cr.Status, + + Instance: cr, + } +} + +func FromClusterCryostat(cr *operatorv1beta1.ClusterCryostat) *CryostatInstance { + return &CryostatInstance{ + Name: cr.Name, + InstallNamespace: cr.Spec.InstallNamespace, + TargetNamespaces: cr.Spec.TargetNamespaces, + + Spec: &cr.Spec.CryostatSpec, + Status: &cr.Status.CryostatStatus, + + Instance: cr, + } +} diff --git a/internal/controllers/openshift.go b/internal/controllers/openshift.go index 3eb26ddc1..c0c4a529c 100644 --- a/internal/controllers/openshift.go +++ b/internal/controllers/openshift.go @@ -41,8 +41,8 @@ import ( "fmt" "regexp" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "github.com/cryostatio/cryostat-operator/internal/controllers/common" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" "github.com/go-logr/logr" configv1 "github.com/openshift/api/config/v1" consolev1 "github.com/openshift/api/console/v1" @@ -52,7 +52,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func (r *CryostatReconciler) reconcileOpenShift(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) reconcileOpenShift(ctx context.Context, cr *model.CryostatInstance) error { if !r.IsOpenShift { return nil } @@ -63,11 +63,11 @@ func (r *CryostatReconciler) reconcileOpenShift(ctx context.Context, cr *operato return r.addCorsAllowedOriginIfNotPresent(ctx, cr) } -func (r *CryostatReconciler) finalizeOpenShift(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) finalizeOpenShift(ctx context.Context, cr *model.CryostatInstance) error { if !r.IsOpenShift { return nil } - reqLogger := r.Log.WithValues("Request.Namespace", cr.Namespace, "Request.Name", cr.Name) + reqLogger := r.Log.WithValues("Request.Namespace", cr.InstallNamespace, "Request.Name", cr.Name) err := r.deleteConsoleLink(ctx, newConsoleLink(cr), reqLogger) if err != nil { return err @@ -75,17 +75,17 @@ func (r *CryostatReconciler) finalizeOpenShift(ctx context.Context, cr *operator return r.deleteCorsAllowedOrigins(ctx, cr) } -func newConsoleLink(cr *operatorv1beta1.Cryostat) *consolev1.ConsoleLink { +func newConsoleLink(cr *model.CryostatInstance) *consolev1.ConsoleLink { // Cluster scoped, so use a unique name to avoid conflicts return &consolev1.ConsoleLink{ ObjectMeta: metav1.ObjectMeta{ - Name: common.ClusterUniqueName(cr), + Name: common.ClusterUniqueName(cr.Name, cr.InstallNamespace), }, } } -func (r *CryostatReconciler) reconcileConsoleLink(ctx context.Context, cr *operatorv1beta1.Cryostat) error { - reqLogger := r.Log.WithValues("Request.Namespace", cr.Namespace, "Request.Name", cr.Name) +func (r *Reconciler) reconcileConsoleLink(ctx context.Context, cr *model.CryostatInstance) error { + reqLogger := r.Log.WithValues("Request.Namespace", cr.InstallNamespace, "Request.Name", cr.Name) link := newConsoleLink(cr) url := cr.Status.ApplicationURL @@ -104,7 +104,7 @@ func (r *CryostatReconciler) reconcileConsoleLink(ctx context.Context, cr *opera } link.Spec.Location = consolev1.NamespaceDashboard link.Spec.NamespaceDashboard = &consolev1.NamespaceDashboardSpec{ - Namespaces: []string{cr.Namespace}, + Namespaces: []string{cr.InstallNamespace}, // TODO should this be on targetNamespaces too? } return nil }) @@ -115,7 +115,7 @@ func (r *CryostatReconciler) reconcileConsoleLink(ctx context.Context, cr *opera return nil } -func (r *CryostatReconciler) deleteConsoleLink(ctx context.Context, link *consolev1.ConsoleLink, logger logr.Logger) error { +func (r *Reconciler) deleteConsoleLink(ctx context.Context, link *consolev1.ConsoleLink, logger logr.Logger) error { err := r.Client.Delete(ctx, link) if err != nil { if kerrors.IsNotFound(err) { @@ -129,8 +129,8 @@ func (r *CryostatReconciler) deleteConsoleLink(ctx context.Context, link *consol return nil } -func (r *CryostatReconciler) addCorsAllowedOriginIfNotPresent(ctx context.Context, cr *operatorv1beta1.Cryostat) error { - reqLogger := r.Log.WithValues("Request.Namespace", cr.Namespace, "Request.Name", cr.Name) +func (r *Reconciler) addCorsAllowedOriginIfNotPresent(ctx context.Context, cr *model.CryostatInstance) error { + reqLogger := r.Log.WithValues("Request.Namespace", cr.InstallNamespace, "Request.Name", cr.Name) allowedOrigin := cr.Status.ApplicationURL if len(allowedOrigin) == 0 { @@ -167,8 +167,8 @@ func (r *CryostatReconciler) addCorsAllowedOriginIfNotPresent(ctx context.Contex return nil } -func (r *CryostatReconciler) deleteCorsAllowedOrigins(ctx context.Context, cr *operatorv1beta1.Cryostat) error { - reqLogger := r.Log.WithValues("Request.Namespace", cr.Namespace, "Request.Name", cr.Name) +func (r *Reconciler) deleteCorsAllowedOrigins(ctx context.Context, cr *model.CryostatInstance) error { + reqLogger := r.Log.WithValues("Request.Namespace", cr.InstallNamespace, "Request.Name", cr.Name) allowedOrigin := cr.Status.ApplicationURL if len(allowedOrigin) == 0 { diff --git a/internal/controllers/pvc.go b/internal/controllers/pvc.go index e63753436..42fc7b88d 100644 --- a/internal/controllers/pvc.go +++ b/internal/controllers/pvc.go @@ -41,6 +41,7 @@ import ( "fmt" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" @@ -51,7 +52,7 @@ import ( // Event type to inform users of invalid PVC specs const eventPersistentVolumeClaimInvalidType = "PersistentVolumeClaimInvalid" -func (r *CryostatReconciler) reconcilePVC(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) reconcilePVC(ctx context.Context, cr *model.CryostatInstance) error { emptyDir := cr.Spec.StorageOptions != nil && cr.Spec.StorageOptions.EmptyDir != nil && cr.Spec.StorageOptions.EmptyDir.Enabled if emptyDir { // If user requested an emptyDir volume, then do nothing. @@ -62,26 +63,26 @@ func (r *CryostatReconciler) reconcilePVC(ctx context.Context, cr *operatorv1bet pvc := &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } // Look up PVC configuration, applying defaults where needed config := configurePVC(cr) - err := r.createOrUpdatePVC(ctx, pvc, cr, config) + err := r.createOrUpdatePVC(ctx, pvc, cr.Instance, config) if err != nil { // If the API server says the PVC is invalid, emit a warning event // to inform the user. if kerrors.IsInvalid(err) { - r.EventRecorder.Event(cr, corev1.EventTypeWarning, eventPersistentVolumeClaimInvalidType, err.Error()) + r.EventRecorder.Event(cr.Instance, corev1.EventTypeWarning, eventPersistentVolumeClaimInvalidType, err.Error()) } return err } return nil } -func (r *CryostatReconciler) createOrUpdatePVC(ctx context.Context, pvc *corev1.PersistentVolumeClaim, +func (r *Reconciler) createOrUpdatePVC(ctx context.Context, pvc *corev1.PersistentVolumeClaim, owner metav1.Object, config *operatorv1beta1.PersistentVolumeClaimConfig) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, pvc, func() error { // Merge labels and annotations to prevent overriding any set by Kubernetes @@ -114,7 +115,7 @@ func (r *CryostatReconciler) createOrUpdatePVC(ctx context.Context, pvc *corev1. return nil } -func configurePVC(cr *operatorv1beta1.Cryostat) *operatorv1beta1.PersistentVolumeClaimConfig { +func configurePVC(cr *model.CryostatInstance) *operatorv1beta1.PersistentVolumeClaimConfig { // Check for PVC config within CR var config *operatorv1beta1.PersistentVolumeClaimConfig if cr.Spec.StorageOptions == nil || cr.Spec.StorageOptions.PVC == nil { diff --git a/internal/controllers/rbac.go b/internal/controllers/rbac.go index 3ff44e34e..6d9d08692 100644 --- a/internal/controllers/rbac.go +++ b/internal/controllers/rbac.go @@ -41,8 +41,8 @@ import ( "encoding/json" "fmt" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "github.com/cryostatio/cryostat-operator/internal/controllers/common" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" oauthv1 "github.com/openshift/api/oauth/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -51,7 +51,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func (r *CryostatReconciler) reconcileRBAC(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +// TODO Use Spec.TargetNamespaces to set up RBAC + +func (r *Reconciler) reconcileRBAC(ctx context.Context, cr *model.CryostatInstance) error { err := r.reconcileServiceAccount(ctx, cr) if err != nil { return err @@ -71,20 +73,20 @@ func (r *CryostatReconciler) reconcileRBAC(ctx context.Context, cr *operatorv1be return nil } -func (r *CryostatReconciler) finalizeRBAC(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) finalizeRBAC(ctx context.Context, cr *model.CryostatInstance) error { return r.deleteClusterRoleBinding(ctx, cr) } -func newServiceAccount(cr *operatorv1beta1.Cryostat) *corev1.ServiceAccount { +func newServiceAccount(cr *model.CryostatInstance) *corev1.ServiceAccount { return &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } } -func (r *CryostatReconciler) reconcileServiceAccount(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) reconcileServiceAccount(ctx context.Context, cr *model.CryostatInstance) error { sa := newServiceAccount(cr) labels := map[string]string{ "app": "cryostat", @@ -108,19 +110,19 @@ func (r *CryostatReconciler) reconcileServiceAccount(ctx context.Context, cr *op annotations["serviceaccounts.openshift.io/oauth-redirectreference.route"] = string(ref) } - return r.createOrUpdateServiceAccount(ctx, sa, cr, labels, annotations) + return r.createOrUpdateServiceAccount(ctx, sa, cr.Instance, labels, annotations) } -func newRole(cr *operatorv1beta1.Cryostat) *rbacv1.Role { +func newRole(cr *model.CryostatInstance) *rbacv1.Role { return &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } } -func (r *CryostatReconciler) reconcileRole(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) reconcileRole(ctx context.Context, cr *model.CryostatInstance) error { role := newRole(cr) rules := []rbacv1.PolicyRule{ { @@ -149,14 +151,14 @@ func (r *CryostatReconciler) reconcileRole(ctx context.Context, cr *operatorv1be Resources: []string{"routes"}, }, } - return r.createOrUpdateRole(ctx, role, cr, rules) + return r.createOrUpdateRole(ctx, role, cr.Instance, rules) } -func (r *CryostatReconciler) reconcileRoleBinding(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) reconcileRoleBinding(ctx context.Context, cr *model.CryostatInstance) error { binding := &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } @@ -175,20 +177,20 @@ func (r *CryostatReconciler) reconcileRoleBinding(ctx context.Context, cr *opera Name: newRole(cr).Name, } - return r.createOrUpdateRoleBinding(ctx, binding, cr, subjects, roleRef) + return r.createOrUpdateRoleBinding(ctx, binding, cr.Instance, subjects, roleRef) } -func newClusterRoleBinding(cr *operatorv1beta1.Cryostat) *rbacv1.ClusterRoleBinding { +func newClusterRoleBinding(cr *model.CryostatInstance) *rbacv1.ClusterRoleBinding { return &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: common.ClusterUniqueName(cr), + Name: common.ClusterUniqueName(cr.Name, cr.InstallNamespace), }, } } const clusterRoleName = "cryostat-operator-cryostat" -func (r *CryostatReconciler) reconcileClusterRoleBinding(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) reconcileClusterRoleBinding(ctx context.Context, cr *model.CryostatInstance) error { binding := newClusterRoleBinding(cr) sa := newServiceAccount(cr) @@ -206,10 +208,10 @@ func (r *CryostatReconciler) reconcileClusterRoleBinding(ctx context.Context, cr Name: clusterRoleName, } - return r.createOrUpdateClusterRoleBinding(ctx, binding, cr, subjects, roleRef) + return r.createOrUpdateClusterRoleBinding(ctx, binding, cr.Instance, subjects, roleRef) } -func (r *CryostatReconciler) createOrUpdateServiceAccount(ctx context.Context, sa *corev1.ServiceAccount, +func (r *Reconciler) createOrUpdateServiceAccount(ctx context.Context, sa *corev1.ServiceAccount, owner metav1.Object, labels map[string]string, annotations map[string]string) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, sa, func() error { // TODO just replace the labels and annotations we manage, once we allow the user to configure @@ -238,7 +240,7 @@ func (r *CryostatReconciler) createOrUpdateServiceAccount(ctx context.Context, s return nil } -func (r *CryostatReconciler) createOrUpdateRole(ctx context.Context, role *rbacv1.Role, +func (r *Reconciler) createOrUpdateRole(ctx context.Context, role *rbacv1.Role, owner metav1.Object, rules []rbacv1.PolicyRule) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, role, func() error { // Update the list of PolicyRules @@ -257,7 +259,7 @@ func (r *CryostatReconciler) createOrUpdateRole(ctx context.Context, role *rbacv return nil } -func (r *CryostatReconciler) createOrUpdateRoleBinding(ctx context.Context, binding *rbacv1.RoleBinding, +func (r *Reconciler) createOrUpdateRoleBinding(ctx context.Context, binding *rbacv1.RoleBinding, owner metav1.Object, subjects []rbacv1.Subject, roleRef *rbacv1.RoleRef) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, binding, func() error { // Update the list of Subjects @@ -278,7 +280,7 @@ func (r *CryostatReconciler) createOrUpdateRoleBinding(ctx context.Context, bind return nil } -func (r *CryostatReconciler) createOrUpdateClusterRoleBinding(ctx context.Context, binding *rbacv1.ClusterRoleBinding, +func (r *Reconciler) createOrUpdateClusterRoleBinding(ctx context.Context, binding *rbacv1.ClusterRoleBinding, owner metav1.Object, subjects []rbacv1.Subject, roleRef *rbacv1.RoleRef) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, binding, func() error { // Update the list of Subjects @@ -296,7 +298,7 @@ func (r *CryostatReconciler) createOrUpdateClusterRoleBinding(ctx context.Contex return nil } -func (r *CryostatReconciler) deleteClusterRoleBinding(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) deleteClusterRoleBinding(ctx context.Context, cr *model.CryostatInstance) error { clusterBinding := newClusterRoleBinding(cr) err := r.Delete(ctx, clusterBinding) if err != nil { diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index 1d1cde124..560e749af 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -44,37 +44,29 @@ import ( "strconv" "time" + operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + "github.com/cryostatio/cryostat-operator/internal/controllers/common" + resources "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" "github.com/go-logr/logr" "github.com/google/go-cmp/cmp" + kerrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - - "github.com/cryostatio/cryostat-operator/internal/controllers/common" - resources "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" - certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" - openshiftv1 "github.com/openshift/api/route/v1" securityv1 "github.com/openshift/api/security/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - netv1 "k8s.io/api/networking/v1" - rbacv1 "k8s.io/api/rbac/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -// Generates constants from environment variables at build time -//go:generate go run ../tools/const_generator.go - -// CryostatReconciler reconciles a Cryostat object -type CryostatReconciler struct { +type ReconcilerConfig struct { client.Client Log logr.Logger Scheme *runtime.Scheme @@ -85,6 +77,11 @@ type CryostatReconciler struct { common.ReconcilerTLS } +type Reconciler struct { + *ReconcilerConfig + updateStatus func(context.Context, runtime.Object) // FIXME +} + // Name used for Finalizer that handles Cryostat deletion const cryostatFinalizer = "operator.cryostat.io/cryostat.finalizer" @@ -129,63 +126,25 @@ var reportsDeploymentConditions = deploymentConditionTypeMap{ operatorv1beta1.ConditionTypeReportsDeploymentReplicaFailure: appsv1.DeploymentReplicaFailure, } -// +kubebuilder:rbac:namespace=system,groups="",resources=pods;services;services/finalizers;endpoints;persistentvolumeclaims;events;configmaps;secrets;serviceaccounts,verbs=* -// +kubebuilder:rbac:namespace=system,groups="",resources=replicationcontrollers,verbs=get -// +kubebuilder:rbac:namespace=system,groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=create;get;list;update;watch;delete -// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=create;get;list;update;watch;delete -// +kubebuilder:rbac:groups=authentication.k8s.io,resources=tokenreviews,verbs=create -// +kubebuilder:rbac:groups=authorization.k8s.io,resources=selfsubjectaccessreviews,verbs=create -// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch -// +kubebuilder:rbac:groups=oauth.openshift.io,resources=oauthaccesstokens,verbs=list;delete -// +kubebuilder:rbac:groups=config.openshift.io,resources=apiservers,verbs=get;list;update;watch -// +kubebuilder:rbac:namespace=system,groups=route.openshift.io,resources=routes;routes/custom-host,verbs=* -// +kubebuilder:rbac:namespace=system,groups=apps.openshift.io,resources=deploymentconfigs,verbs=get -// +kubebuilder:rbac:namespace=system,groups=apps,resources=deployments;daemonsets;replicasets;statefulsets,verbs=* -// +kubebuilder:rbac:namespace=system,groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;create -// +kubebuilder:rbac:namespace=system,groups=cert-manager.io,resources=issuers;certificates,verbs=create;get;list;update;watch;delete -// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=cryostats,verbs=* -// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=cryostats/status,verbs=get;update;patch -// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=cryostats/finalizers,verbs=update -// +kubebuilder:rbac:groups=console.openshift.io,resources=consolelinks,verbs=get;create;list;update;delete -// +kubebuilder:rbac:namespace=system,groups=networking.k8s.io,resources=ingresses,verbs=* - -// Reconcile processes a Cryostat CR and manages a Cryostat installation accordingly -func (r *CryostatReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { - reqLogger := r.Log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - - reqLogger.Info("Reconciling Cryostat") - - // Fetch the Cryostat instance - instance := &operatorv1beta1.Cryostat{} - err := r.Client.Get(ctx, request.NamespacedName, instance) - if err != nil { - if kerrors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - reqLogger.Info("Cryostat instance not found") - return reconcile.Result{}, nil - } - reqLogger.Error(err, "Error reading Cryostat instance") - return reconcile.Result{}, err - } +func (r *Reconciler) ReconcileCryostat(ctx context.Context, cr *model.CryostatInstance) (ctrl.Result, error) { + reqLogger := r.Log.WithValues("Request.Namespace", cr.InstallNamespace, "Request.Name", cr.Name) // Check if this Cryostat is being deleted - if instance.GetDeletionTimestamp() != nil { - if controllerutil.ContainsFinalizer(instance, cryostatFinalizer) { + if cr.Instance.GetDeletionTimestamp() != nil { + if controllerutil.ContainsFinalizer(cr.Instance, cryostatFinalizer) { // Perform finalizer logic related to RBAC objects - err = r.finalizeRBAC(ctx, instance) + err := r.finalizeRBAC(ctx, cr) if err != nil { return reconcile.Result{}, err } // OpenShift-specific - err = r.finalizeOpenShift(ctx, instance) + err = r.finalizeOpenShift(ctx, cr) if err != nil { return reconcile.Result{}, err } - err = common.RemoveFinalizer(ctx, r.Client, instance, cryostatFinalizer) + err = common.RemoveFinalizer(ctx, r.Client, cr.Instance, cryostatFinalizer) if err != nil { return reconcile.Result{}, err } @@ -195,32 +154,32 @@ func (r *CryostatReconciler) Reconcile(ctx context.Context, request ctrl.Request } // Add our finalizer, so we can clean up Cryostat resources upon deletion - if !controllerutil.ContainsFinalizer(instance, cryostatFinalizer) { - err := common.AddFinalizer(ctx, r.Client, instance, cryostatFinalizer) + if !controllerutil.ContainsFinalizer(cr.Instance, cryostatFinalizer) { + err := common.AddFinalizer(ctx, r.Client, cr.Instance, cryostatFinalizer) if err != nil { return reconcile.Result{}, err } } - reqLogger.Info("Spec", "Minimal", instance.Spec.Minimal) + reqLogger.Info("Spec", "Minimal", cr.Spec.Minimal) - err = r.reconcilePVC(ctx, instance) + err := r.reconcilePVC(ctx, cr) if err != nil { return reconcile.Result{}, err } - err = r.reconcileSecrets(ctx, instance) + err = r.reconcileSecrets(ctx, cr) if err != nil { return reconcile.Result{}, err } // Set up TLS using cert-manager, if available var tlsConfig *resources.TLSConfig - if r.IsCertManagerEnabled(instance) { - tlsConfig, err = r.setupTLS(ctx, instance) + if r.IsCertManagerEnabled(cr) { + tlsConfig, err = r.setupTLS(ctx, cr) if err != nil { if err == common.ErrCertNotReady { - condErr := r.updateCondition(ctx, instance, operatorv1beta1.ConditionTypeTLSSetupComplete, metav1.ConditionFalse, + condErr := r.updateCondition(ctx, cr, operatorv1beta1.ConditionTypeTLSSetupComplete, metav1.ConditionFalse, reasonWaitingForCert, "Waiting for certificates to become ready.") if condErr != nil { return reconcile.Result{}, err @@ -228,20 +187,20 @@ func (r *CryostatReconciler) Reconcile(ctx context.Context, request ctrl.Request return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } if err == errCertManagerMissing { - r.updateCondition(ctx, instance, operatorv1beta1.ConditionTypeTLSSetupComplete, metav1.ConditionFalse, + r.updateCondition(ctx, cr, operatorv1beta1.ConditionTypeTLSSetupComplete, metav1.ConditionFalse, reasonCertManagerUnavailable, eventCertManagerUnavailableMsg) } reqLogger.Error(err, "Failed to set up TLS for Cryostat") return reconcile.Result{}, err } - err = r.updateCondition(ctx, instance, operatorv1beta1.ConditionTypeTLSSetupComplete, metav1.ConditionTrue, + err = r.updateCondition(ctx, cr, operatorv1beta1.ConditionTypeTLSSetupComplete, metav1.ConditionTrue, reasonAllCertsReady, "All certificates for Cryostat components are ready.") if err != nil { return reconcile.Result{}, err } } else { - err = r.updateCondition(ctx, instance, operatorv1beta1.ConditionTypeTLSSetupComplete, metav1.ConditionTrue, + err = r.updateCondition(ctx, cr, operatorv1beta1.ConditionTypeTLSSetupComplete, metav1.ConditionTrue, reasonCertManagerDisabled, "TLS setup has been disabled.") if err != nil { return reconcile.Result{}, err @@ -249,109 +208,86 @@ func (r *CryostatReconciler) Reconcile(ctx context.Context, request ctrl.Request } // Reconcile RBAC resources for Cryostat - err = r.reconcileRBAC(ctx, instance) + err = r.reconcileRBAC(ctx, cr) if err != nil { return reconcile.Result{}, err } serviceSpecs := &resources.ServiceSpecs{} - err = r.reconcileGrafanaService(ctx, instance, tlsConfig, serviceSpecs) + err = r.reconcileGrafanaService(ctx, cr, tlsConfig, serviceSpecs) if err != nil { return requeueIfIngressNotReady(reqLogger, err) } - err = r.reconcileCoreService(ctx, instance, tlsConfig, serviceSpecs) + err = r.reconcileCoreService(ctx, cr, tlsConfig, serviceSpecs) if err != nil { return requeueIfIngressNotReady(reqLogger, err) } imageTags := r.getImageTags() - fsGroup, err := r.getFSGroup(ctx, instance.Namespace) + fsGroup, err := r.getFSGroup(ctx, cr.InstallNamespace) if err != nil { return reconcile.Result{}, err } - reportsResult, err := r.reconcileReports(ctx, reqLogger, instance, tlsConfig, imageTags, serviceSpecs) + reportsResult, err := r.reconcileReports(ctx, reqLogger, cr, tlsConfig, imageTags, serviceSpecs) if err != nil { return reportsResult, err } - deployment := resources.NewDeploymentForCR(instance, serviceSpecs, imageTags, tlsConfig, *fsGroup, r.IsOpenShift) - err = r.createOrUpdateDeployment(ctx, deployment, instance) + deployment := resources.NewDeploymentForCR(cr, serviceSpecs, imageTags, tlsConfig, *fsGroup, r.IsOpenShift) + err = r.createOrUpdateDeployment(ctx, deployment, cr.Instance) if err != nil { return reconcile.Result{}, err } if serviceSpecs.CoreURL != nil { - instance.Status.ApplicationURL = serviceSpecs.CoreURL.String() - err = r.Client.Status().Update(ctx, instance) + cr.Status.ApplicationURL = serviceSpecs.CoreURL.String() + err = r.Client.Status().Update(ctx, cr.Instance) if err != nil { return reconcile.Result{}, err } } // OpenShift-specific - err = r.reconcileOpenShift(ctx, instance) + err = r.reconcileOpenShift(ctx, cr) if err != nil { return reconcile.Result{}, err } // Check deployment status and update conditions - err = r.updateConditionsFromDeployment(ctx, instance, types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, + err = r.updateConditionsFromDeployment(ctx, cr, types.NamespacedName{Name: deployment.Name, Namespace: deployment.Namespace}, mainDeploymentConditions) if err != nil { return reconcile.Result{}, err } - reqLogger.Info("Successfully reconciled deployment", "Deployment.Namespace", deployment.Namespace, "Deployment.Name", deployment.Name) + reqLogger.Info("Successfully reconciled Cryostat") return reconcile.Result{}, nil } -// SetupWithManager sets up the controller with the Manager. -func (r *CryostatReconciler) SetupWithManager(mgr ctrl.Manager) error { - c := ctrl.NewControllerManagedBy(mgr). - For(&operatorv1beta1.Cryostat{}) - - // Watch for changes to secondary resources and requeue the owner Cryostat - resources := []client.Object{&appsv1.Deployment{}, &corev1.Service{}, &corev1.Secret{}, &corev1.PersistentVolumeClaim{}, - &corev1.ServiceAccount{}, &rbacv1.Role{}, &rbacv1.RoleBinding{}, &netv1.Ingress{}} - if r.IsOpenShift { - resources = append(resources, &openshiftv1.Route{}) - } - // Can only check this at startup - if r.IsCertManagerInstalled { - resources = append(resources, &certv1.Issuer{}, &certv1.Certificate{}) - } - - for _, resource := range resources { - c = c.Owns(resource) - } - - return c.Complete(r) -} - -func (r *CryostatReconciler) reconcileReports(ctx context.Context, reqLogger logr.Logger, instance *operatorv1beta1.Cryostat, +func (r *Reconciler) reconcileReports(ctx context.Context, reqLogger logr.Logger, cr *model.CryostatInstance, tls *resources.TLSConfig, imageTags *resources.ImageTags, serviceSpecs *resources.ServiceSpecs) (reconcile.Result, error) { - reqLogger.Info("Spec", "Reports", instance.Spec.ReportOptions) + reqLogger.Info("Spec", "Reports", cr.Spec.ReportOptions) desired := int32(0) - if instance.Spec.ReportOptions != nil { - desired = instance.Spec.ReportOptions.Replicas + if cr.Spec.ReportOptions != nil { + desired = cr.Spec.ReportOptions.Replicas } - err := r.reconcileReportsService(ctx, instance, tls, serviceSpecs) + err := r.reconcileReportsService(ctx, cr, tls, serviceSpecs) if err != nil { return reconcile.Result{}, err } - deployment := resources.NewDeploymentForReports(instance, imageTags, tls, r.IsOpenShift) + deployment := resources.NewDeploymentForReports(cr, imageTags, tls, r.IsOpenShift) if desired == 0 { if err := r.Client.Delete(ctx, deployment); err != nil && !kerrors.IsNotFound(err) { return reconcile.Result{}, err } - removeConditionIfPresent(instance, operatorv1beta1.ConditionTypeReportsDeploymentAvailable, + removeConditionIfPresent(cr, operatorv1beta1.ConditionTypeReportsDeploymentAvailable, operatorv1beta1.ConditionTypeReportsDeploymentProgressing, operatorv1beta1.ConditionTypeReportsDeploymentReplicaFailure) - err := r.Client.Status().Update(ctx, instance) + err := r.Client.Status().Update(ctx, cr.Instance) if err != nil { return reconcile.Result{}, err } @@ -359,13 +295,13 @@ func (r *CryostatReconciler) reconcileReports(ctx context.Context, reqLogger log } if desired > 0 { - err = r.createOrUpdateDeployment(ctx, deployment, instance) + err = r.createOrUpdateDeployment(ctx, deployment, cr.Instance) if err != nil { return reconcile.Result{}, err } // Check deployment status and update conditions - err = r.updateConditionsFromDeployment(ctx, instance, types.NamespacedName{Name: deployment.Name, Namespace: deployment.Namespace}, + err = r.updateConditionsFromDeployment(ctx, cr, types.NamespacedName{Name: deployment.Name, Namespace: deployment.Namespace}, reportsDeploymentConditions) if err != nil { return reconcile.Result{}, err @@ -374,7 +310,7 @@ func (r *CryostatReconciler) reconcileReports(ctx context.Context, reqLogger log return reconcile.Result{}, nil } -func (r *CryostatReconciler) getImageTags() *resources.ImageTags { +func (r *Reconciler) getImageTags() *resources.ImageTags { return &resources.ImageTags{ CoreImageTag: r.getEnvOrDefault(coreImageTagEnv, DefaultCoreImageTag), DatasourceImageTag: r.getEnvOrDefault(datasourceImageTagEnv, DefaultDatasourceImageTag), @@ -383,7 +319,7 @@ func (r *CryostatReconciler) getImageTags() *resources.ImageTags { } } -func (r *CryostatReconciler) getEnvOrDefault(name string, defaultVal string) string { +func (r *Reconciler) getEnvOrDefault(name string, defaultVal string) string { val := r.GetEnv(name) if len(val) > 0 { return val @@ -394,7 +330,7 @@ func (r *CryostatReconciler) getEnvOrDefault(name string, defaultVal string) str // fsGroup to use when not constrained const defaultFSGroup int64 = 18500 -func (r *CryostatReconciler) getFSGroup(ctx context.Context, namespace string) (*int64, error) { +func (r *Reconciler) getFSGroup(ctx context.Context, namespace string) (*int64, error) { if r.IsOpenShift { // Check namespace for supplemental groups annotation ns := &corev1.Namespace{} @@ -426,25 +362,25 @@ func parseSupGroups(supGroups string) (*int64, error) { return &gid, nil } -func (r *CryostatReconciler) updateCondition(ctx context.Context, cr *operatorv1beta1.Cryostat, +func (r *Reconciler) updateCondition(ctx context.Context, cr *model.CryostatInstance, condType operatorv1beta1.CryostatConditionType, status metav1.ConditionStatus, reason string, message string) error { - reqLogger := r.Log.WithValues("Request.Namespace", cr.Namespace, "Request.Name", cr.Name) + reqLogger := r.Log.WithValues("Request.Namespace", cr.InstallNamespace, "Request.Name", cr.Name) meta.SetStatusCondition(&cr.Status.Conditions, metav1.Condition{ Type: string(condType), Status: status, Reason: reason, Message: message, }) - err := r.Client.Status().Update(ctx, cr) + err := r.Client.Status().Update(ctx, cr.Instance) if err != nil { reqLogger.Error(err, "failed to update condition", "type", condType) } return err } -func (r *CryostatReconciler) updateConditionsFromDeployment(ctx context.Context, cr *operatorv1beta1.Cryostat, +func (r *Reconciler) updateConditionsFromDeployment(ctx context.Context, cr *model.CryostatInstance, deployKey types.NamespacedName, mapping deploymentConditionTypeMap) error { - reqLogger := r.Log.WithValues("Request.Namespace", cr.Namespace, "Request.Name", cr.Name) + reqLogger := r.Log.WithValues("Request.Namespace", cr.InstallNamespace, "Request.Name", cr.Name) // Get deployment's latest conditions deploy := &appsv1.Deployment{} @@ -467,7 +403,7 @@ func (r *CryostatReconciler) updateConditionsFromDeployment(ctx context.Context, }) } } - err = r.Client.Status().Update(ctx, cr) + err = r.Client.Status().Update(ctx, cr.Instance) if err != nil { reqLogger.Error(err, "failed to update conditions for deployment", "deployment", deploy.Name) } @@ -476,7 +412,7 @@ func (r *CryostatReconciler) updateConditionsFromDeployment(ctx context.Context, var errSelectorModified error = errors.New("deployment selector has been modified") -func (r *CryostatReconciler) createOrUpdateDeployment(ctx context.Context, deploy *appsv1.Deployment, owner metav1.Object) error { +func (r *Reconciler) createOrUpdateDeployment(ctx context.Context, deploy *appsv1.Deployment, owner metav1.Object) error { deployCopy := deploy.DeepCopy() op, err := controllerutil.CreateOrUpdate(ctx, r.Client, deploy, func() error { // TODO consider managing labels and annotations using CRD @@ -514,7 +450,7 @@ func (r *CryostatReconciler) createOrUpdateDeployment(ctx context.Context, deplo return nil } -func (r *CryostatReconciler) recreateDeployment(ctx context.Context, deploy *appsv1.Deployment, owner metav1.Object) error { +func (r *Reconciler) recreateDeployment(ctx context.Context, deploy *appsv1.Deployment, owner metav1.Object) error { // Delete and recreate deployment err := r.deleteDeployment(ctx, deploy) if err != nil { @@ -523,7 +459,7 @@ func (r *CryostatReconciler) recreateDeployment(ctx context.Context, deploy *app return r.createOrUpdateDeployment(ctx, deploy, owner) } -func (r *CryostatReconciler) deleteDeployment(ctx context.Context, deploy *appsv1.Deployment) error { +func (r *Reconciler) deleteDeployment(ctx context.Context, deploy *appsv1.Deployment) error { err := r.Client.Delete(ctx, deploy) if err != nil && !kerrors.IsNotFound(err) { r.Log.Error(err, "Could not delete deployment", "name", deploy.Name, "namespace", deploy.Namespace) @@ -540,7 +476,7 @@ func requeueIfIngressNotReady(log logr.Logger, err error) (reconcile.Result, err return reconcile.Result{}, err } -func removeConditionIfPresent(cr *operatorv1beta1.Cryostat, condType ...operatorv1beta1.CryostatConditionType) { +func removeConditionIfPresent(cr *model.CryostatInstance, condType ...operatorv1beta1.CryostatConditionType) { for _, ct := range condType { found := meta.FindStatusCondition(cr.Status.Conditions, string(ct)) if found != nil { diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index 0f5d993c3..bdcedf94e 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -79,6 +79,7 @@ type cryostatTestInput struct { *test.TestResources } +// TODO Can we move the specs to a separate common file and execute them with each type of CRD? var _ = Describe("CryostatController", func() { var t *cryostatTestInput @@ -92,7 +93,7 @@ var _ = Describe("CryostatController", func() { err := test.SetCreationTimestamp(t.objs...) Expect(err).ToNot(HaveOccurred()) t.Client = fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(t.objs...).Build() - t.controller = &controllers.CryostatReconciler{ + t.controller = controllers.NewCryostatReconciler(&controllers.ReconcilerConfig{ Client: test.NewClientWithTimestamp(test.NewTestClient(t.Client, t.TestResources)), Scheme: s, IsOpenShift: t.OpenShift, @@ -100,7 +101,7 @@ var _ = Describe("CryostatController", func() { RESTMapper: test.NewTESTRESTMapper(), Log: logger, ReconcilerTLS: test.NewTestReconcilerTLS(&t.TestReconcilerConfig), - } + }) }) BeforeEach(func() { diff --git a/internal/controllers/routes.go b/internal/controllers/routes.go index 74ca5c30c..a11abb1d6 100644 --- a/internal/controllers/routes.go +++ b/internal/controllers/routes.go @@ -43,7 +43,9 @@ import ( "net/url" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + "github.com/cryostatio/cryostat-operator/internal/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" routev1 "github.com/openshift/api/route/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -51,16 +53,16 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func newCoreRoute(cr *operatorv1beta1.Cryostat) *routev1.Route { +func newCoreRoute(cr *model.CryostatInstance) *routev1.Route { return &routev1.Route{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } } -func (r *CryostatReconciler) reconcileCoreRoute(ctx context.Context, svc *corev1.Service, cr *operatorv1beta1.Cryostat, +func (r *Reconciler) reconcileCoreRoute(ctx context.Context, svc *corev1.Service, cr *model.CryostatInstance, tls *resource_definitions.TLSConfig, specs *resource_definitions.ServiceSpecs) error { route := newCoreRoute(cr) coreConfig := configureCoreRoute(cr) @@ -72,12 +74,12 @@ func (r *CryostatReconciler) reconcileCoreRoute(ctx context.Context, svc *corev1 return nil } -func (r *CryostatReconciler) reconcileGrafanaRoute(ctx context.Context, svc *corev1.Service, cr *operatorv1beta1.Cryostat, +func (r *Reconciler) reconcileGrafanaRoute(ctx context.Context, svc *corev1.Service, cr *model.CryostatInstance, tls *resource_definitions.TLSConfig, specs *resource_definitions.ServiceSpecs) error { route := &routev1.Route{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-grafana", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } if cr.Spec.Minimal { @@ -97,13 +99,13 @@ func (r *CryostatReconciler) reconcileGrafanaRoute(ctx context.Context, svc *cor // so that they may be accessed outside of the cluster var ErrIngressNotReady = goerrors.New("ingress configuration not yet available") -func (r *CryostatReconciler) reconcileRoute(ctx context.Context, route *routev1.Route, svc *corev1.Service, - cr *operatorv1beta1.Cryostat, tls *resource_definitions.TLSConfig, config *operatorv1beta1.NetworkConfiguration) (*url.URL, error) { +func (r *Reconciler) reconcileRoute(ctx context.Context, route *routev1.Route, svc *corev1.Service, + cr *model.CryostatInstance, tls *resource_definitions.TLSConfig, config *operatorv1beta1.NetworkConfiguration) (*url.URL, error) { port, err := getHTTPPort(svc) if err != nil { return nil, err } - route, err = r.createOrUpdateRoute(ctx, route, cr, svc, port, tls, config) + route, err = r.createOrUpdateRoute(ctx, route, cr.Instance, svc, port, tls, config) if err != nil { return nil, err } @@ -119,7 +121,7 @@ func (r *CryostatReconciler) reconcileRoute(ctx context.Context, route *routev1. }, nil } -func (r *CryostatReconciler) createOrUpdateRoute(ctx context.Context, route *routev1.Route, owner metav1.Object, +func (r *Reconciler) createOrUpdateRoute(ctx context.Context, route *routev1.Route, owner metav1.Object, svc *corev1.Service, exposePort *corev1.ServicePort, tlsConfig *resource_definitions.TLSConfig, config *operatorv1beta1.NetworkConfiguration) (*routev1.Route, error) { // Use edge termination by default var routeTLS *routev1.TLSConfig @@ -164,7 +166,7 @@ func getProtocol(route *routev1.Route) string { return "https" } -func (r *CryostatReconciler) deleteRoute(ctx context.Context, route *routev1.Route) error { +func (r *Reconciler) deleteRoute(ctx context.Context, route *routev1.Route) error { err := r.Client.Delete(ctx, route) if err != nil && !errors.IsNotFound(err) { r.Log.Error(err, "Could not delete route", "name", route.Name, "namespace", route.Namespace) @@ -176,15 +178,15 @@ func (r *CryostatReconciler) deleteRoute(ctx context.Context, route *routev1.Rou func getHTTPPort(svc *corev1.Service) (*corev1.ServicePort, error) { for _, port := range svc.Spec.Ports { - if port.Name == httpPortName { + if port.Name == constants.HttpPortName { return &port, nil } } // Shouldn't happen - return nil, fmt.Errorf("no \"%s\"port in %s service in %s namespace", httpPortName, svc.Name, svc.Namespace) + return nil, fmt.Errorf("no \"%s\"port in %s service in %s namespace", constants.HttpPortName, svc.Name, svc.Namespace) } -func configureCoreRoute(cr *operatorv1beta1.Cryostat) *operatorv1beta1.NetworkConfiguration { +func configureCoreRoute(cr *model.CryostatInstance) *operatorv1beta1.NetworkConfiguration { var config *operatorv1beta1.NetworkConfiguration if cr.Spec.NetworkOptions == nil || cr.Spec.NetworkOptions.CoreConfig == nil { config = &operatorv1beta1.NetworkConfiguration{} @@ -196,7 +198,7 @@ func configureCoreRoute(cr *operatorv1beta1.Cryostat) *operatorv1beta1.NetworkCo return config } -func configureGrafanaRoute(cr *operatorv1beta1.Cryostat) *operatorv1beta1.NetworkConfiguration { +func configureGrafanaRoute(cr *model.CryostatInstance) *operatorv1beta1.NetworkConfiguration { var config *operatorv1beta1.NetworkConfiguration if cr.Spec.NetworkOptions == nil || cr.Spec.NetworkOptions.GrafanaConfig == nil { config = &operatorv1beta1.NetworkConfiguration{} diff --git a/internal/controllers/secrets.go b/internal/controllers/secrets.go index 2ee2e7719..514edad97 100644 --- a/internal/controllers/secrets.go +++ b/internal/controllers/secrets.go @@ -40,15 +40,14 @@ import ( "context" "fmt" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - + "github.com/cryostatio/cryostat-operator/internal/controllers/model" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func (r *CryostatReconciler) reconcileSecrets(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) reconcileSecrets(ctx context.Context, cr *model.CryostatInstance) error { if err := r.reconcileGrafanaSecret(ctx, cr); err != nil { return err } @@ -58,11 +57,11 @@ func (r *CryostatReconciler) reconcileSecrets(ctx context.Context, cr *operatorv return r.reconcileJMXSecret(ctx, cr) } -func (r *CryostatReconciler) reconcileGrafanaSecret(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) reconcileGrafanaSecret(ctx context.Context, cr *model.CryostatInstance) error { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-grafana-basic", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } @@ -74,7 +73,7 @@ func (r *CryostatReconciler) reconcileGrafanaSecret(ctx context.Context, cr *ope } secretName = "" } else { - err := r.createOrUpdateSecret(ctx, secret, cr, func() error { + err := r.createOrUpdateSecret(ctx, secret, cr.Instance, func() error { if secret.StringData == nil { secret.StringData = map[string]string{} } @@ -94,7 +93,7 @@ func (r *CryostatReconciler) reconcileGrafanaSecret(ctx context.Context, cr *ope // Set the Grafana secret in the CR status cr.Status.GrafanaSecret = secretName - return r.Client.Status().Update(ctx, cr) + return r.Client.Status().Update(ctx, cr.Instance) } // jmxSecretNameSuffix is the suffix to be appended to the name of a @@ -107,15 +106,15 @@ const jmxSecretUserKey = "CRYOSTAT_RJMX_USER" // jmxSecretPassKey indexes the password within the Cryostat JMX auth secret const jmxSecretPassKey = "CRYOSTAT_RJMX_PASS" -func (r *CryostatReconciler) reconcileJMXSecret(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) reconcileJMXSecret(ctx context.Context, cr *model.CryostatInstance) error { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + jmxSecretNameSuffix, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } - return r.createOrUpdateSecret(ctx, secret, cr, func() error { + return r.createOrUpdateSecret(ctx, secret, cr.Instance, func() error { if secret.StringData == nil { secret.StringData = map[string]string{} } @@ -136,11 +135,11 @@ const databaseSecretNameSuffix = "-jmx-credentials-db" // dbSecretUserKey indexes the password within the Cryostat JMX credentials database Secret const databaseSecretPassKey = "CRYOSTAT_JMX_CREDENTIALS_DB_PASSWORD" -func (r *CryostatReconciler) reconcileDatabaseSecret(ctx context.Context, cr *operatorv1beta1.Cryostat) error { +func (r *Reconciler) reconcileDatabaseSecret(ctx context.Context, cr *model.CryostatInstance) error { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + databaseSecretNameSuffix, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } @@ -149,7 +148,7 @@ func (r *CryostatReconciler) reconcileDatabaseSecret(ctx context.Context, cr *op return nil // Do not delete default secret to allow reverting to use default if needed } - return r.createOrUpdateSecret(ctx, secret, cr, func() error { + return r.createOrUpdateSecret(ctx, secret, cr.Instance, func() error { if secret.StringData == nil { secret.StringData = map[string]string{} } @@ -162,7 +161,7 @@ func (r *CryostatReconciler) reconcileDatabaseSecret(ctx context.Context, cr *op }) } -func (r *CryostatReconciler) createOrUpdateSecret(ctx context.Context, secret *corev1.Secret, owner metav1.Object, +func (r *Reconciler) createOrUpdateSecret(ctx context.Context, secret *corev1.Secret, owner metav1.Object, delegate controllerutil.MutateFn) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, secret, func() error { // Set the Cryostat CR as controller @@ -179,7 +178,7 @@ func (r *CryostatReconciler) createOrUpdateSecret(ctx context.Context, secret *c return nil } -func (r *CryostatReconciler) deleteSecret(ctx context.Context, secret *corev1.Secret) error { +func (r *Reconciler) deleteSecret(ctx context.Context, secret *corev1.Secret) error { err := r.Client.Delete(ctx, secret) if err != nil && !errors.IsNotFound(err) { r.Log.Error(err, "Could not delete secret", "name", secret.Name, "namespace", secret.Namespace) diff --git a/internal/controllers/services.go b/internal/controllers/services.go index ab017f8c6..bc0b7eabc 100644 --- a/internal/controllers/services.go +++ b/internal/controllers/services.go @@ -43,7 +43,9 @@ import ( "strconv" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + "github.com/cryostatio/cryostat-operator/internal/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -51,28 +53,17 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -// FIXME duplicated from resource_definitions.go -const ( - cryostatHTTPContainerPort int32 = 8181 - cryostatJMXContainerPort int32 = 9091 - grafanaContainerPort int32 = 3000 - datasourceContainerPort int32 = 8080 - reportsContainerPort int32 = 10000 - loopbackAddress string = "127.0.0.1" - httpPortName string = "http" -) - -func (r *CryostatReconciler) reconcileCoreService(ctx context.Context, cr *operatorv1beta1.Cryostat, +func (r *Reconciler) reconcileCoreService(ctx context.Context, cr *model.CryostatInstance, tls *resource_definitions.TLSConfig, specs *resource_definitions.ServiceSpecs) error { svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } config := configureCoreService(cr) - err := r.createOrUpdateService(ctx, svc, cr, &config.ServiceConfig, func() error { + err := r.createOrUpdateService(ctx, svc, cr.Instance, &config.ServiceConfig, func() error { svc.Spec.Selector = map[string]string{ "app": cr.Name, "component": "cryostat", @@ -81,12 +72,12 @@ func (r *CryostatReconciler) reconcileCoreService(ctx context.Context, cr *opera { Name: "http", Port: *config.HTTPPort, - TargetPort: intstr.IntOrString{IntVal: cryostatHTTPContainerPort}, + TargetPort: intstr.IntOrString{IntVal: constants.CryostatHTTPContainerPort}, }, { Name: "jfr-jmx", Port: *config.JMXPort, - TargetPort: intstr.IntOrString{IntVal: cryostatJMXContainerPort}, + TargetPort: intstr.IntOrString{IntVal: constants.CryostatJMXContainerPort}, }, } return nil @@ -102,12 +93,12 @@ func (r *CryostatReconciler) reconcileCoreService(ctx context.Context, cr *opera } } -func (r *CryostatReconciler) reconcileGrafanaService(ctx context.Context, cr *operatorv1beta1.Cryostat, +func (r *Reconciler) reconcileGrafanaService(ctx context.Context, cr *model.CryostatInstance, tls *resource_definitions.TLSConfig, specs *resource_definitions.ServiceSpecs) error { svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-grafana", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } @@ -119,7 +110,7 @@ func (r *CryostatReconciler) reconcileGrafanaService(ctx context.Context, cr *op } } else { config := configureGrafanaService(cr) - err := r.createOrUpdateService(ctx, svc, cr, &config.ServiceConfig, func() error { + err := r.createOrUpdateService(ctx, svc, cr.Instance, &config.ServiceConfig, func() error { svc.Spec.Selector = map[string]string{ "app": cr.Name, "component": "cryostat", @@ -145,13 +136,13 @@ func (r *CryostatReconciler) reconcileGrafanaService(ctx context.Context, cr *op } } -func (r *CryostatReconciler) reconcileReportsService(ctx context.Context, cr *operatorv1beta1.Cryostat, +func (r *Reconciler) reconcileReportsService(ctx context.Context, cr *model.CryostatInstance, tls *resource_definitions.TLSConfig, specs *resource_definitions.ServiceSpecs) error { config := configureReportsService(cr) svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-reports", - Namespace: cr.Namespace, + Namespace: cr.InstallNamespace, }, } @@ -159,7 +150,7 @@ func (r *CryostatReconciler) reconcileReportsService(ctx context.Context, cr *op // Delete service if it exists return r.deleteService(ctx, svc) } - err := r.createOrUpdateService(ctx, svc, cr, &config.ServiceConfig, func() error { + err := r.createOrUpdateService(ctx, svc, cr.Instance, &config.ServiceConfig, func() error { svc.Spec.Selector = map[string]string{ "app": cr.Name, "component": "reports", @@ -168,7 +159,7 @@ func (r *CryostatReconciler) reconcileReportsService(ctx context.Context, cr *op { Name: "http", Port: *config.HTTPPort, - TargetPort: intstr.IntOrString{IntVal: reportsContainerPort}, + TargetPort: intstr.IntOrString{IntVal: constants.ReportsContainerPort}, }, } return nil @@ -189,7 +180,7 @@ func (r *CryostatReconciler) reconcileReportsService(ctx context.Context, cr *op return nil } -func configureCoreService(cr *operatorv1beta1.Cryostat) *operatorv1beta1.CoreServiceConfig { +func configureCoreService(cr *model.CryostatInstance) *operatorv1beta1.CoreServiceConfig { // Check CR for config var config *operatorv1beta1.CoreServiceConfig if cr.Spec.ServiceOptions == nil || cr.Spec.ServiceOptions.CoreConfig == nil { @@ -203,18 +194,18 @@ func configureCoreService(cr *operatorv1beta1.Cryostat) *operatorv1beta1.CoreSer // Apply default HTTP and JMX port if not provided if config.HTTPPort == nil { - httpPort := cryostatHTTPContainerPort + httpPort := constants.CryostatHTTPContainerPort config.HTTPPort = &httpPort } if config.JMXPort == nil { - jmxPort := cryostatJMXContainerPort + jmxPort := constants.CryostatJMXContainerPort config.JMXPort = &jmxPort } return config } -func configureGrafanaService(cr *operatorv1beta1.Cryostat) *operatorv1beta1.GrafanaServiceConfig { +func configureGrafanaService(cr *model.CryostatInstance) *operatorv1beta1.GrafanaServiceConfig { // Check CR for config var config *operatorv1beta1.GrafanaServiceConfig if cr.Spec.ServiceOptions == nil || cr.Spec.ServiceOptions.GrafanaConfig == nil { @@ -228,14 +219,14 @@ func configureGrafanaService(cr *operatorv1beta1.Cryostat) *operatorv1beta1.Graf // Apply default HTTP port if not provided if config.HTTPPort == nil { - httpPort := grafanaContainerPort + httpPort := constants.GrafanaContainerPort config.HTTPPort = &httpPort } return config } -func configureReportsService(cr *operatorv1beta1.Cryostat) *operatorv1beta1.ReportsServiceConfig { +func configureReportsService(cr *model.CryostatInstance) *operatorv1beta1.ReportsServiceConfig { // Check CR for config var config *operatorv1beta1.ReportsServiceConfig if cr.Spec.ServiceOptions == nil || cr.Spec.ServiceOptions.ReportsConfig == nil { @@ -249,7 +240,7 @@ func configureReportsService(cr *operatorv1beta1.Cryostat) *operatorv1beta1.Repo // Apply default HTTP port if not provided if config.HTTPPort == nil { - httpPort := reportsContainerPort + httpPort := constants.ReportsContainerPort config.HTTPPort = &httpPort } @@ -273,7 +264,7 @@ func configureService(config *operatorv1beta1.ServiceConfig, appLabel string, co config.Labels["component"] = componentLabel } -func (r *CryostatReconciler) createOrUpdateService(ctx context.Context, svc *corev1.Service, owner metav1.Object, +func (r *Reconciler) createOrUpdateService(ctx context.Context, svc *corev1.Service, owner metav1.Object, config *operatorv1beta1.ServiceConfig, delegate controllerutil.MutateFn) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, svc, func() error { // Update labels and annotations @@ -295,7 +286,7 @@ func (r *CryostatReconciler) createOrUpdateService(ctx context.Context, svc *cor return nil } -func (r *CryostatReconciler) deleteService(ctx context.Context, svc *corev1.Service) error { +func (r *Reconciler) deleteService(ctx context.Context, svc *corev1.Service) error { err := r.Client.Delete(ctx, svc) if err != nil && !errors.IsNotFound(err) { r.Log.Error(err, "Could not delete service", "name", svc.Name, "namespace", svc.Namespace) diff --git a/internal/main.go b/internal/main.go index 6ea862f9d..1ae75c5b4 100644 --- a/internal/main.go +++ b/internal/main.go @@ -138,7 +138,7 @@ func main() { certManager := isCertManagerInstalled(apiResources) - if err = (&controllers.CryostatReconciler{ + if err = (controllers.NewCryostatReconciler(&controllers.ReconcilerConfig{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("Cryostat"), Scheme: mgr.GetScheme(), @@ -149,7 +149,7 @@ func main() { ReconcilerTLS: common.NewReconcilerTLS(&common.ReconcilerTLSConfig{ Client: mgr.GetClient(), }), - }).SetupWithManager(mgr); err != nil { + })).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Cryostat") os.Exit(1) } From 15c6d22850fbb05a16d632c140d088e20d41964d Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Mon, 23 Jan 2023 16:06:21 -0500 Subject: [PATCH 02/21] Use abstraction for tests --- .../bases/operator.cryostat.io_cryostats.yaml | 4478 ----------------- config/rbac/role.yaml | 20 + internal/controllers/model/instance.go | 7 +- internal/controllers/reconciler_test.go | 430 +- internal/test/resources.go | 156 +- 5 files changed, 318 insertions(+), 4773 deletions(-) diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 56398625c..a5da61ab2 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -16,12 +16,6 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - jsonPath: .status.applicationUrl - name: Application URL - type: string - - jsonPath: .status.grafanaSecret - name: Grafana Secret - type: string - jsonPath: .status.applicationUrl name: Application URL type: string @@ -4502,4475 +4496,3 @@ spec: storage: true subresources: status: {} - - name: v1beta1 - schema: - openAPIV3Schema: - description: Cryostat contains configuration options for controlling the Deployment - of the Cryostat application and its related components. A Cryostat instance - must be created to instruct the operator to deploy the Cryostat application. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: CryostatSpec defines the desired state of Cryostat. - properties: - authProperties: - description: Override default authorization properties for Cryostat - on OpenShift. - properties: - clusterRoleName: - description: 'Name of the ClusterRole to use when Cryostat requests - a role-scoped OAuth token. This ClusterRole should contain permissions - for all Kubernetes objects listed in custom permission mapping. - More details: https://docs.openshift.com/container-platform/4.11/authentication/tokens-scoping.html#scoping-tokens-role-scope_configuring-internal-oauth' - type: string - configMapName: - description: Name of config map in the local namespace. - type: string - filename: - description: Filename within config map containing the resource - mapping. - type: string - required: - - clusterRoleName - - configMapName - - filename - type: object - enableCertManager: - description: Use cert-manager to secure in-cluster communication between - Cryostat components. Requires cert-manager to be installed. - type: boolean - eventTemplates: - description: List of Flight Recorder Event Templates to preconfigure - in Cryostat. - items: - description: A ConfigMap containing a .jfc template file. - properties: - configMapName: - description: Name of config map in the local namespace. - type: string - filename: - description: Filename within config map containing the template - file. - type: string - required: - - configMapName - - filename - type: object - type: array - jmxCacheOptions: - description: Options to customize the JMX target connections cache - for the Cryostat application. - properties: - targetCacheSize: - description: The maximum number of JMX connections to cache. Use - `-1` for an unlimited cache size (TTL expiration only). Defaults - to `-1`. - format: int32 - minimum: -1 - type: integer - targetCacheTTL: - description: The time to live (in seconds) for cached JMX connections. - Defaults to `10`. - format: int32 - minimum: 1 - type: integer - type: object - jmxCredentialsDatabaseOptions: - description: Options to configure the Cryostat application's JMX credentials - database. - properties: - databaseSecretName: - description: Name of the secret containing the password to encrypt - JMX credentials database. - type: string - type: object - maxWsConnections: - description: The maximum number of WebSocket client connections allowed - (minimum 1, default unlimited). - format: int32 - minimum: 1 - type: integer - minimal: - description: Deploy a pared-down Cryostat instance with no Grafana - Dashboard or JFR Data Source. - type: boolean - networkOptions: - description: Options to control how the operator exposes the application - outside of the cluster, such as using an Ingress or Route. - properties: - commandConfig: - description: "Specifications for how to expose the Cryostat command - service, which serves the WebSocket command channel. \n Deprecated: - CommandConfig is no longer used." - properties: - annotations: - additionalProperties: - type: string - description: Annotations to add to the Ingress or Route during - its creation. - type: object - ingressSpec: - description: Configuration for an Ingress object. Currently - subpaths are not supported, so unique hosts must be specified - (if a single external IP is being used) to differentiate - between ingresses/services. - properties: - defaultBackend: - description: DefaultBackend is the backend that should - handle requests that don't match any rule. If Rules - are not specified, DefaultBackend must be specified. - If DefaultBackend is not set, the handling of requests - that do not match any of the rules will be up to the - Ingress controller. - properties: - resource: - description: Resource is an ObjectRef to another Kubernetes - resource in the namespace of the Ingress object. - If resource is specified, a service.Name and service.Port - must not be specified. This is a mutually exclusive - setting with "Service". - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is - required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - required: - - kind - - name - type: object - service: - description: Service references a Service as a Backend. - This is a mutually exclusive setting with "Resource". - properties: - name: - description: Name is the referenced service. The - service must exist in the same namespace as - the Ingress object. - type: string - port: - description: Port of the referenced service. A - port name or port number is required for a IngressServiceBackend. - properties: - name: - description: Name is the name of the port - on the Service. This is a mutually exclusive - setting with "Number". - type: string - number: - description: Number is the numerical port - number (e.g. 80) on the Service. This is - a mutually exclusive setting with "Name". - format: int32 - type: integer - type: object - required: - - name - type: object - type: object - ingressClassName: - description: IngressClassName is the name of the IngressClass - cluster resource. The associated IngressClass defines - which controller will implement the resource. This replaces - the deprecated `kubernetes.io/ingress.class` annotation. - For backwards compatibility, when that annotation is - set, it must be given precedence over this field. The - controller may emit a warning if the field and annotation - have different values. Implementations of this API should - ignore Ingresses without a class specified. An IngressClass - resource may be marked as default, which can be used - to set a default value for this field. For more information, - refer to the IngressClass documentation. - type: string - rules: - description: A list of host rules used to configure the - Ingress. If unspecified, or no rule matches, all traffic - is sent to the default backend. - items: - description: IngressRule represents the rules mapping - the paths under a specified host to the related backend - services. Incoming requests are first evaluated for - a host match, then routed to the backend associated - with the matching IngressRuleValue. - properties: - host: - description: "Host is the fully qualified domain - name of a network host, as defined by RFC 3986. - Note the following deviations from the \"host\" - part of the URI as defined in RFC 3986: 1. IPs - are not allowed. Currently an IngressRuleValue - can only apply to the IP in the Spec of the parent - Ingress. 2. The `:` delimiter is not respected - because ports are not allowed. Currently the port - of an Ingress is implicitly :80 for http and :443 - for https. Both these may change in the future. - Incoming requests are matched against the host - before the IngressRuleValue. If the host is unspecified, - the Ingress routes all traffic based on the specified - IngressRuleValue. \n Host can be \"precise\" which - is a domain name without the terminating dot of - a network host (e.g. \"foo.bar.com\") or \"wildcard\", - which is a domain name prefixed with a single - wildcard label (e.g. \"*.foo.com\"). The wildcard - character '*' must appear by itself as the first - DNS label and matches only a single label. You - cannot have a wildcard label by itself (e.g. Host - == \"*\"). Requests will be matched against the - Host field in the following way: 1. If Host is - precise, the request matches this rule if the - http host header is equal to Host. 2. If Host - is a wildcard, then the request matches this rule - if the http host header is to equal to the suffix - (removing the first label) of the wildcard rule." - type: string - http: - description: 'HTTPIngressRuleValue is a list of - http selectors pointing to backends. In the example: - http:///? -> backend where - where parts of the url correspond to RFC 3986, - this resource will be used to match against everything - after the last ''/'' and before the first ''?'' - or ''#''.' - properties: - paths: - description: A collection of paths that map - requests to backends. - items: - description: HTTPIngressPath associates a - path with a backend. Incoming urls matching - the path are forwarded to the backend. - properties: - backend: - description: Backend defines the referenced - service endpoint to which the traffic - will be forwarded to. - properties: - resource: - description: Resource is an ObjectRef - to another Kubernetes resource in - the namespace of the Ingress object. - If resource is specified, a service.Name - and service.Port must not be specified. - This is a mutually exclusive setting - with "Service". - properties: - apiGroup: - description: APIGroup is the group - for the resource being referenced. - If APIGroup is not specified, - the specified Kind must be in - the core API group. For any - other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type - of resource being referenced - type: string - name: - description: Name is the name - of resource being referenced - type: string - required: - - kind - - name - type: object - service: - description: Service references a - Service as a Backend. This is a - mutually exclusive setting with - "Resource". - properties: - name: - description: Name is the referenced - service. The service must exist - in the same namespace as the - Ingress object. - type: string - port: - description: Port of the referenced - service. A port name or port - number is required for a IngressServiceBackend. - properties: - name: - description: Name is the name - of the port on the Service. - This is a mutually exclusive - setting with "Number". - type: string - number: - description: Number is the - numerical port number (e.g. - 80) on the Service. This - is a mutually exclusive - setting with "Name". - format: int32 - type: integer - type: object - required: - - name - type: object - type: object - path: - description: Path is matched against the - path of an incoming request. Currently - it can contain characters disallowed - from the conventional "path" part of - a URL as defined by RFC 3986. Paths - must begin with a '/' and must be present - when using PathType with value "Exact" - or "Prefix". - type: string - pathType: - description: 'PathType determines the - interpretation of the Path matching. - PathType can be one of the following - values: * Exact: Matches the URL path - exactly. * Prefix: Matches based on - a URL path prefix split by ''/''. Matching - is done on a path element by element - basis. A path element refers is the - list of labels in the path split by - the ''/'' separator. A request is a - match for path p if every p is an element-wise - prefix of p of the request path. Note - that if the last element of the path - is a substring of the last element in - request path, it is not a match (e.g. - /foo/bar matches /foo/bar/baz, but does - not match /foo/barbaz). * ImplementationSpecific: - Interpretation of the Path matching - is up to the IngressClass. Implementations - can treat this as a separate PathType - or treat it identically to Prefix or - Exact path types. Implementations are - required to support all path types.' - type: string - required: - - backend - - pathType - type: object - type: array - x-kubernetes-list-type: atomic - required: - - paths - type: object - type: object - type: array - x-kubernetes-list-type: atomic - tls: - description: TLS configuration. Currently the Ingress - only supports a single TLS port, 443. If multiple members - of this list specify different hosts, they will be multiplexed - on the same port according to the hostname specified - through the SNI TLS extension, if the ingress controller - fulfilling the ingress supports SNI. - items: - description: IngressTLS describes the transport layer - security associated with an Ingress. - properties: - hosts: - description: Hosts are a list of hosts included - in the TLS certificate. The values in this list - must match the name/s used in the tlsSecret. Defaults - to the wildcard host setting for the loadbalancer - controller fulfilling this Ingress, if left unspecified. - items: - type: string - type: array - x-kubernetes-list-type: atomic - secretName: - description: SecretName is the name of the secret - used to terminate TLS traffic on port 443. Field - is left optional to allow TLS routing based on - SNI hostname alone. If the SNI host in a listener - conflicts with the "Host" header field used by - an IngressRule, the SNI host is used for termination - and value of the Host header is used for routing. - type: string - type: object - type: array - x-kubernetes-list-type: atomic - type: object - labels: - additionalProperties: - type: string - description: Labels to add to the Ingress or Route during - its creation. The label with key "app" is reserved for use - by the operator. - type: object - type: object - coreConfig: - description: Specifications for how to expose the Cryostat service, - which serves the Cryostat application. - properties: - annotations: - additionalProperties: - type: string - description: Annotations to add to the Ingress or Route during - its creation. - type: object - ingressSpec: - description: Configuration for an Ingress object. Currently - subpaths are not supported, so unique hosts must be specified - (if a single external IP is being used) to differentiate - between ingresses/services. - properties: - defaultBackend: - description: DefaultBackend is the backend that should - handle requests that don't match any rule. If Rules - are not specified, DefaultBackend must be specified. - If DefaultBackend is not set, the handling of requests - that do not match any of the rules will be up to the - Ingress controller. - properties: - resource: - description: Resource is an ObjectRef to another Kubernetes - resource in the namespace of the Ingress object. - If resource is specified, a service.Name and service.Port - must not be specified. This is a mutually exclusive - setting with "Service". - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is - required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - required: - - kind - - name - type: object - service: - description: Service references a Service as a Backend. - This is a mutually exclusive setting with "Resource". - properties: - name: - description: Name is the referenced service. The - service must exist in the same namespace as - the Ingress object. - type: string - port: - description: Port of the referenced service. A - port name or port number is required for a IngressServiceBackend. - properties: - name: - description: Name is the name of the port - on the Service. This is a mutually exclusive - setting with "Number". - type: string - number: - description: Number is the numerical port - number (e.g. 80) on the Service. This is - a mutually exclusive setting with "Name". - format: int32 - type: integer - type: object - required: - - name - type: object - type: object - ingressClassName: - description: IngressClassName is the name of the IngressClass - cluster resource. The associated IngressClass defines - which controller will implement the resource. This replaces - the deprecated `kubernetes.io/ingress.class` annotation. - For backwards compatibility, when that annotation is - set, it must be given precedence over this field. The - controller may emit a warning if the field and annotation - have different values. Implementations of this API should - ignore Ingresses without a class specified. An IngressClass - resource may be marked as default, which can be used - to set a default value for this field. For more information, - refer to the IngressClass documentation. - type: string - rules: - description: A list of host rules used to configure the - Ingress. If unspecified, or no rule matches, all traffic - is sent to the default backend. - items: - description: IngressRule represents the rules mapping - the paths under a specified host to the related backend - services. Incoming requests are first evaluated for - a host match, then routed to the backend associated - with the matching IngressRuleValue. - properties: - host: - description: "Host is the fully qualified domain - name of a network host, as defined by RFC 3986. - Note the following deviations from the \"host\" - part of the URI as defined in RFC 3986: 1. IPs - are not allowed. Currently an IngressRuleValue - can only apply to the IP in the Spec of the parent - Ingress. 2. The `:` delimiter is not respected - because ports are not allowed. Currently the port - of an Ingress is implicitly :80 for http and :443 - for https. Both these may change in the future. - Incoming requests are matched against the host - before the IngressRuleValue. If the host is unspecified, - the Ingress routes all traffic based on the specified - IngressRuleValue. \n Host can be \"precise\" which - is a domain name without the terminating dot of - a network host (e.g. \"foo.bar.com\") or \"wildcard\", - which is a domain name prefixed with a single - wildcard label (e.g. \"*.foo.com\"). The wildcard - character '*' must appear by itself as the first - DNS label and matches only a single label. You - cannot have a wildcard label by itself (e.g. Host - == \"*\"). Requests will be matched against the - Host field in the following way: 1. If Host is - precise, the request matches this rule if the - http host header is equal to Host. 2. If Host - is a wildcard, then the request matches this rule - if the http host header is to equal to the suffix - (removing the first label) of the wildcard rule." - type: string - http: - description: 'HTTPIngressRuleValue is a list of - http selectors pointing to backends. In the example: - http:///? -> backend where - where parts of the url correspond to RFC 3986, - this resource will be used to match against everything - after the last ''/'' and before the first ''?'' - or ''#''.' - properties: - paths: - description: A collection of paths that map - requests to backends. - items: - description: HTTPIngressPath associates a - path with a backend. Incoming urls matching - the path are forwarded to the backend. - properties: - backend: - description: Backend defines the referenced - service endpoint to which the traffic - will be forwarded to. - properties: - resource: - description: Resource is an ObjectRef - to another Kubernetes resource in - the namespace of the Ingress object. - If resource is specified, a service.Name - and service.Port must not be specified. - This is a mutually exclusive setting - with "Service". - properties: - apiGroup: - description: APIGroup is the group - for the resource being referenced. - If APIGroup is not specified, - the specified Kind must be in - the core API group. For any - other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type - of resource being referenced - type: string - name: - description: Name is the name - of resource being referenced - type: string - required: - - kind - - name - type: object - service: - description: Service references a - Service as a Backend. This is a - mutually exclusive setting with - "Resource". - properties: - name: - description: Name is the referenced - service. The service must exist - in the same namespace as the - Ingress object. - type: string - port: - description: Port of the referenced - service. A port name or port - number is required for a IngressServiceBackend. - properties: - name: - description: Name is the name - of the port on the Service. - This is a mutually exclusive - setting with "Number". - type: string - number: - description: Number is the - numerical port number (e.g. - 80) on the Service. This - is a mutually exclusive - setting with "Name". - format: int32 - type: integer - type: object - required: - - name - type: object - type: object - path: - description: Path is matched against the - path of an incoming request. Currently - it can contain characters disallowed - from the conventional "path" part of - a URL as defined by RFC 3986. Paths - must begin with a '/' and must be present - when using PathType with value "Exact" - or "Prefix". - type: string - pathType: - description: 'PathType determines the - interpretation of the Path matching. - PathType can be one of the following - values: * Exact: Matches the URL path - exactly. * Prefix: Matches based on - a URL path prefix split by ''/''. Matching - is done on a path element by element - basis. A path element refers is the - list of labels in the path split by - the ''/'' separator. A request is a - match for path p if every p is an element-wise - prefix of p of the request path. Note - that if the last element of the path - is a substring of the last element in - request path, it is not a match (e.g. - /foo/bar matches /foo/bar/baz, but does - not match /foo/barbaz). * ImplementationSpecific: - Interpretation of the Path matching - is up to the IngressClass. Implementations - can treat this as a separate PathType - or treat it identically to Prefix or - Exact path types. Implementations are - required to support all path types.' - type: string - required: - - backend - - pathType - type: object - type: array - x-kubernetes-list-type: atomic - required: - - paths - type: object - type: object - type: array - x-kubernetes-list-type: atomic - tls: - description: TLS configuration. Currently the Ingress - only supports a single TLS port, 443. If multiple members - of this list specify different hosts, they will be multiplexed - on the same port according to the hostname specified - through the SNI TLS extension, if the ingress controller - fulfilling the ingress supports SNI. - items: - description: IngressTLS describes the transport layer - security associated with an Ingress. - properties: - hosts: - description: Hosts are a list of hosts included - in the TLS certificate. The values in this list - must match the name/s used in the tlsSecret. Defaults - to the wildcard host setting for the loadbalancer - controller fulfilling this Ingress, if left unspecified. - items: - type: string - type: array - x-kubernetes-list-type: atomic - secretName: - description: SecretName is the name of the secret - used to terminate TLS traffic on port 443. Field - is left optional to allow TLS routing based on - SNI hostname alone. If the SNI host in a listener - conflicts with the "Host" header field used by - an IngressRule, the SNI host is used for termination - and value of the Host header is used for routing. - type: string - type: object - type: array - x-kubernetes-list-type: atomic - type: object - labels: - additionalProperties: - type: string - description: Labels to add to the Ingress or Route during - its creation. The label with key "app" is reserved for use - by the operator. - type: object - type: object - grafanaConfig: - description: Specifications for how to expose Cryostat's Grafana - service, which serves the Grafana dashboard. - properties: - annotations: - additionalProperties: - type: string - description: Annotations to add to the Ingress or Route during - its creation. - type: object - ingressSpec: - description: Configuration for an Ingress object. Currently - subpaths are not supported, so unique hosts must be specified - (if a single external IP is being used) to differentiate - between ingresses/services. - properties: - defaultBackend: - description: DefaultBackend is the backend that should - handle requests that don't match any rule. If Rules - are not specified, DefaultBackend must be specified. - If DefaultBackend is not set, the handling of requests - that do not match any of the rules will be up to the - Ingress controller. - properties: - resource: - description: Resource is an ObjectRef to another Kubernetes - resource in the namespace of the Ingress object. - If resource is specified, a service.Name and service.Port - must not be specified. This is a mutually exclusive - setting with "Service". - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is - required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - required: - - kind - - name - type: object - service: - description: Service references a Service as a Backend. - This is a mutually exclusive setting with "Resource". - properties: - name: - description: Name is the referenced service. The - service must exist in the same namespace as - the Ingress object. - type: string - port: - description: Port of the referenced service. A - port name or port number is required for a IngressServiceBackend. - properties: - name: - description: Name is the name of the port - on the Service. This is a mutually exclusive - setting with "Number". - type: string - number: - description: Number is the numerical port - number (e.g. 80) on the Service. This is - a mutually exclusive setting with "Name". - format: int32 - type: integer - type: object - required: - - name - type: object - type: object - ingressClassName: - description: IngressClassName is the name of the IngressClass - cluster resource. The associated IngressClass defines - which controller will implement the resource. This replaces - the deprecated `kubernetes.io/ingress.class` annotation. - For backwards compatibility, when that annotation is - set, it must be given precedence over this field. The - controller may emit a warning if the field and annotation - have different values. Implementations of this API should - ignore Ingresses without a class specified. An IngressClass - resource may be marked as default, which can be used - to set a default value for this field. For more information, - refer to the IngressClass documentation. - type: string - rules: - description: A list of host rules used to configure the - Ingress. If unspecified, or no rule matches, all traffic - is sent to the default backend. - items: - description: IngressRule represents the rules mapping - the paths under a specified host to the related backend - services. Incoming requests are first evaluated for - a host match, then routed to the backend associated - with the matching IngressRuleValue. - properties: - host: - description: "Host is the fully qualified domain - name of a network host, as defined by RFC 3986. - Note the following deviations from the \"host\" - part of the URI as defined in RFC 3986: 1. IPs - are not allowed. Currently an IngressRuleValue - can only apply to the IP in the Spec of the parent - Ingress. 2. The `:` delimiter is not respected - because ports are not allowed. Currently the port - of an Ingress is implicitly :80 for http and :443 - for https. Both these may change in the future. - Incoming requests are matched against the host - before the IngressRuleValue. If the host is unspecified, - the Ingress routes all traffic based on the specified - IngressRuleValue. \n Host can be \"precise\" which - is a domain name without the terminating dot of - a network host (e.g. \"foo.bar.com\") or \"wildcard\", - which is a domain name prefixed with a single - wildcard label (e.g. \"*.foo.com\"). The wildcard - character '*' must appear by itself as the first - DNS label and matches only a single label. You - cannot have a wildcard label by itself (e.g. Host - == \"*\"). Requests will be matched against the - Host field in the following way: 1. If Host is - precise, the request matches this rule if the - http host header is equal to Host. 2. If Host - is a wildcard, then the request matches this rule - if the http host header is to equal to the suffix - (removing the first label) of the wildcard rule." - type: string - http: - description: 'HTTPIngressRuleValue is a list of - http selectors pointing to backends. In the example: - http:///? -> backend where - where parts of the url correspond to RFC 3986, - this resource will be used to match against everything - after the last ''/'' and before the first ''?'' - or ''#''.' - properties: - paths: - description: A collection of paths that map - requests to backends. - items: - description: HTTPIngressPath associates a - path with a backend. Incoming urls matching - the path are forwarded to the backend. - properties: - backend: - description: Backend defines the referenced - service endpoint to which the traffic - will be forwarded to. - properties: - resource: - description: Resource is an ObjectRef - to another Kubernetes resource in - the namespace of the Ingress object. - If resource is specified, a service.Name - and service.Port must not be specified. - This is a mutually exclusive setting - with "Service". - properties: - apiGroup: - description: APIGroup is the group - for the resource being referenced. - If APIGroup is not specified, - the specified Kind must be in - the core API group. For any - other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type - of resource being referenced - type: string - name: - description: Name is the name - of resource being referenced - type: string - required: - - kind - - name - type: object - service: - description: Service references a - Service as a Backend. This is a - mutually exclusive setting with - "Resource". - properties: - name: - description: Name is the referenced - service. The service must exist - in the same namespace as the - Ingress object. - type: string - port: - description: Port of the referenced - service. A port name or port - number is required for a IngressServiceBackend. - properties: - name: - description: Name is the name - of the port on the Service. - This is a mutually exclusive - setting with "Number". - type: string - number: - description: Number is the - numerical port number (e.g. - 80) on the Service. This - is a mutually exclusive - setting with "Name". - format: int32 - type: integer - type: object - required: - - name - type: object - type: object - path: - description: Path is matched against the - path of an incoming request. Currently - it can contain characters disallowed - from the conventional "path" part of - a URL as defined by RFC 3986. Paths - must begin with a '/' and must be present - when using PathType with value "Exact" - or "Prefix". - type: string - pathType: - description: 'PathType determines the - interpretation of the Path matching. - PathType can be one of the following - values: * Exact: Matches the URL path - exactly. * Prefix: Matches based on - a URL path prefix split by ''/''. Matching - is done on a path element by element - basis. A path element refers is the - list of labels in the path split by - the ''/'' separator. A request is a - match for path p if every p is an element-wise - prefix of p of the request path. Note - that if the last element of the path - is a substring of the last element in - request path, it is not a match (e.g. - /foo/bar matches /foo/bar/baz, but does - not match /foo/barbaz). * ImplementationSpecific: - Interpretation of the Path matching - is up to the IngressClass. Implementations - can treat this as a separate PathType - or treat it identically to Prefix or - Exact path types. Implementations are - required to support all path types.' - type: string - required: - - backend - - pathType - type: object - type: array - x-kubernetes-list-type: atomic - required: - - paths - type: object - type: object - type: array - x-kubernetes-list-type: atomic - tls: - description: TLS configuration. Currently the Ingress - only supports a single TLS port, 443. If multiple members - of this list specify different hosts, they will be multiplexed - on the same port according to the hostname specified - through the SNI TLS extension, if the ingress controller - fulfilling the ingress supports SNI. - items: - description: IngressTLS describes the transport layer - security associated with an Ingress. - properties: - hosts: - description: Hosts are a list of hosts included - in the TLS certificate. The values in this list - must match the name/s used in the tlsSecret. Defaults - to the wildcard host setting for the loadbalancer - controller fulfilling this Ingress, if left unspecified. - items: - type: string - type: array - x-kubernetes-list-type: atomic - secretName: - description: SecretName is the name of the secret - used to terminate TLS traffic on port 443. Field - is left optional to allow TLS routing based on - SNI hostname alone. If the SNI host in a listener - conflicts with the "Host" header field used by - an IngressRule, the SNI host is used for termination - and value of the Host header is used for routing. - type: string - type: object - type: array - x-kubernetes-list-type: atomic - type: object - labels: - additionalProperties: - type: string - description: Labels to add to the Ingress or Route during - its creation. The label with key "app" is reserved for use - by the operator. - type: object - type: object - type: object - reportOptions: - description: Options to configure Cryostat Automated Report Analysis. - properties: - replicas: - description: The number of report sidecar replica containers to - deploy. Each replica can service one report generation request - at a time. - format: int32 - type: integer - resources: - description: The resources allocated to each sidecar replica. - A replica with more resources can handle larger input recordings - and will process them faster. - properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - schedulingOptions: - description: Options to configure scheduling for the reports deployment - properties: - affinity: - description: Affinity rules for scheduling Cryostat pods. - properties: - nodeAffinity: - description: 'Node affinity scheduling rules for a Cryostat - pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule - pods to nodes that satisfy the affinity expressions - specified by this field, but it may choose a node - that violates one or more of the expressions. The - node that is most preferred is the one with the - greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, - etc.), compute a sum by iterating through the elements - of this field and adding "weight" to the sum if - the node matches the corresponding matchExpressions; - the node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term - matches all objects with implicit weight 0 (i.e. - it's a no-op). A null preferred scheduling term - matches no objects (i.e. is also a no-op). - properties: - preference: - description: A node selector term, associated - with the corresponding weight. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: The label key that the - selector applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. - If the operator is Gt or Lt, the - values array must have a single - element, which will be interpreted - as an integer. This array is replaced - during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: The label key that the - selector applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. - If the operator is Gt or Lt, the - values array must have a single - element, which will be interpreted - as an integer. This array is replaced - during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - weight: - description: Weight associated with matching - the corresponding nodeSelectorTerm, in the - range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified - by this field are not met at scheduling time, the - pod will not be scheduled onto the node. If the - affinity requirements specified by this field cease - to be met at some point during pod execution (e.g. - due to an update), the system may or may not try - to eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: Required. A list of node selector - terms. The terms are ORed. - items: - description: A null or empty node selector term - matches no objects. The requirements of them - are ANDed. The TopologySelectorTerm type implements - a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: The label key that the - selector applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. - If the operator is Gt or Lt, the - values array must have a single - element, which will be interpreted - as an integer. This array is replaced - during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: The label key that the - selector applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. - If the operator is Gt or Lt, the - values array must have a single - element, which will be interpreted - as an integer. This array is replaced - during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - type: array - required: - - nodeSelectorTerms - type: object - type: object - podAffinity: - description: 'Pod affinity scheduling rules for a Cryostat - pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule - pods to nodes that satisfy the affinity expressions - specified by this field, but it may choose a node - that violates one or more of the expressions. The - node that is most preferred is the one with the - greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, - etc.), compute a sum by iterating through the elements - of this field and adding "weight" to the sum if - the node has pods which matches the corresponding - podAffinityTerm; the node(s) with the highest sum - are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, - associated with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of - resources, in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: operator represents - a key's relationship to a set - of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array - of string values. If the operator - is In or NotIn, the values array - must be non-empty. If the operator - is Exists or DoesNotExist, the - values array must be empty. - This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of - {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. - type: object - type: object - namespaceSelector: - description: A label query over the set - of namespaces that the term applies to. - The term is applied to the union of the - namespaces selected by this field and - the ones listed in the namespaces field. - null selector and null or empty namespaces - list means "this pod's namespace". An - empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: operator represents - a key's relationship to a set - of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array - of string values. If the operator - is In or NotIn, the values array - must be non-empty. If the operator - is Exists or DoesNotExist, the - values array must be empty. - This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of - {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. - type: object - type: object - namespaces: - description: namespaces specifies a static - list of namespace names that the term - applies to. The term is applied to the - union of the namespaces listed in this - field and the ones selected by namespaceSelector. - null or empty namespaces list and null - namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located - (affinity) or not co-located (anti-affinity) - with the pods matching the labelSelector - in the specified namespaces, where co-located - is defined as running on a node whose - value of the label with key topologyKey - matches that of any node on which any - of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching - the corresponding podAffinityTerm, in the - range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified - by this field are not met at scheduling time, the - pod will not be scheduled onto the node. If the - affinity requirements specified by this field cease - to be met at some point during pod execution (e.g. - due to a pod label update), the system may or may - not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes - corresponding to each podAffinityTerm are intersected, - i.e. all terms must be satisfied. - items: - description: Defines a set of pods (namely those - matching the labelSelector relative to the given - namespace(s)) that this pod should be co-located - (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node - whose value of the label with key - matches that of any node on which a pod of the - set of pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by - this field and the ones listed in the namespaces - field. null selector and null or empty namespaces - list means "this pod's namespace". An empty - selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. - The term is applied to the union of the namespaces - listed in this field and the ones selected - by namespaceSelector. null or empty namespaces - list and null namespaceSelector means "this - pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the - pods matching the labelSelector in the specified - namespaces, where co-located is defined as - running on a node whose value of the label - with key topologyKey matches that of any node - on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - podAntiAffinity: - description: 'Pod anti-affinity scheduling rules for a - Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule - pods to nodes that satisfy the anti-affinity expressions - specified by this field, but it may choose a node - that violates one or more of the expressions. The - node that is most preferred is the one with the - greatest sum of weights, i.e. for each node that - meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity - expressions, etc.), compute a sum by iterating through - the elements of this field and adding "weight" to - the sum if the node has pods which matches the corresponding - podAffinityTerm; the node(s) with the highest sum - are the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, - associated with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of - resources, in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: operator represents - a key's relationship to a set - of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array - of string values. If the operator - is In or NotIn, the values array - must be non-empty. If the operator - is Exists or DoesNotExist, the - values array must be empty. - This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of - {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. - type: object - type: object - namespaceSelector: - description: A label query over the set - of namespaces that the term applies to. - The term is applied to the union of the - namespaces selected by this field and - the ones listed in the namespaces field. - null selector and null or empty namespaces - list means "this pod's namespace". An - empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label - key that the selector applies - to. - type: string - operator: - description: operator represents - a key's relationship to a set - of values. Valid operators are - In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array - of string values. If the operator - is In or NotIn, the values array - must be non-empty. If the operator - is Exists or DoesNotExist, the - values array must be empty. - This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of - {key,value} pairs. A single {key,value} - in the matchLabels map is equivalent - to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are - ANDed. - type: object - type: object - namespaces: - description: namespaces specifies a static - list of namespace names that the term - applies to. The term is applied to the - union of the namespaces listed in this - field and the ones selected by namespaceSelector. - null or empty namespaces list and null - namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located - (affinity) or not co-located (anti-affinity) - with the pods matching the labelSelector - in the specified namespaces, where co-located - is defined as running on a node whose - value of the label with key topologyKey - matches that of any node on which any - of the selected pods is running. Empty - topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching - the corresponding podAffinityTerm, in the - range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified - by this field are not met at scheduling time, the - pod will not be scheduled onto the node. If the - anti-affinity requirements specified by this field - cease to be met at some point during pod execution - (e.g. due to a pod label update), the system may - or may not try to eventually evict the pod from - its node. When there are multiple elements, the - lists of nodes corresponding to each podAffinityTerm - are intersected, i.e. all terms must be satisfied. - items: - description: Defines a set of pods (namely those - matching the labelSelector relative to the given - namespace(s)) that this pod should be co-located - (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node - whose value of the label with key - matches that of any node on which a pod of the - set of pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by - this field and the ones listed in the namespaces - field. null selector and null or empty namespaces - list means "this pod's namespace". An empty - selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. - The term is applied to the union of the namespaces - listed in this field and the ones selected - by namespaceSelector. null or empty namespaces - list and null namespaceSelector means "this - pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the - pods matching the labelSelector in the specified - namespaces, where co-located is defined as - running on a node whose value of the label - with key topologyKey matches that of any node - on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - type: object - nodeSelector: - additionalProperties: - type: string - description: 'Label selector used to schedule a Cryostat pod - to a node. See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' - type: object - tolerations: - description: 'Tolerations to allow scheduling of Cryostat - pods to tainted nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' - items: - description: The pod this Toleration is attached to tolerates - any taint that matches the triple using - the matching operator . - properties: - effect: - description: Effect indicates the taint effect to match. - Empty means match all taint effects. When specified, - allowed values are NoSchedule, PreferNoSchedule and - NoExecute. - type: string - key: - description: Key is the taint key that the toleration - applies to. Empty means match all taint keys. If the - key is empty, operator must be Exists; this combination - means to match all values and all keys. - type: string - operator: - description: Operator represents a key's relationship - to the value. Valid operators are Exists and Equal. - Defaults to Equal. Exists is equivalent to wildcard - for value, so that a pod can tolerate all taints of - a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period - of time the toleration (which must be of effect NoExecute, - otherwise this field is ignored) tolerates the taint. - By default, it is not set, which means tolerate the - taint forever (do not evict). Zero and negative values - will be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration - matches to. If the operator is Exists, the value should - be empty, otherwise just a regular string. - type: string - type: object - type: array - type: object - securityOptions: - description: Options to configure the Security Contexts for the - Cryostat report generator. - properties: - podSecurityContext: - description: Security Context to apply to the Cryostat report - generator pod. - properties: - fsGroup: - description: "A special supplemental group that applies - to all containers in a pod. Some volume types allow - the Kubelet to change the ownership of that volume to - be owned by the pod: \n 1. The owning GID will be the - FSGroup 2. The setgid bit is set (new files created - in the volume will be owned by FSGroup) 3. The permission - bits are OR'd with rw-rw---- \n If unset, the Kubelet - will not modify the ownership and permissions of any - volume. Note that this field cannot be set when spec.os.name - is windows." - format: int64 - type: integer - fsGroupChangePolicy: - description: 'fsGroupChangePolicy defines behavior of - changing ownership and permission of the volume before - being exposed inside Pod. This field will only apply - to volume types which support fsGroup based ownership(and - permissions). It will have no effect on ephemeral volume - types such as: secret, configmaps and emptydir. Valid - values are "OnRootMismatch" and "Always". If not specified, - "Always" is used. Note that this field cannot be set - when spec.os.name is windows.' - type: string - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be - set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. Note that this - field cannot be set when spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as - a non-root user. If true, the Kubelet will validate - the image at runtime to ensure that it does not run - as UID 0 (root) and fail to start the container if it - does. If unset or false, no such validation will be - performed. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata - if unspecified. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence - for that container. Note that this field cannot be set - when spec.os.name is windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to all - containers. If unspecified, the container runtime will - allocate a random SELinux context for each container. May - also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. Note that this - field cannot be set when spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by the containers - in this pod. Note that this field cannot be set when - spec.os.name is windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile - defined in a file on the node should be used. The - profile must be preconfigured on the node to work. - Must be a descending path, relative to the kubelet's - configured seccomp profile location. Must only be - set if type is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp - profile will be applied. Valid options are: \n Localhost - - a profile defined in a file on the node should - be used. RuntimeDefault - the container runtime - default profile should be used. Unconfined - no - profile should be applied." - type: string - required: - - type - type: object - supplementalGroups: - description: A list of groups applied to the first process - run in each container, in addition to the container's - primary GID. If unspecified, no groups will be added - to any container. Note that this field cannot be set - when spec.os.name is windows. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls - used for the pod. Pods with unsupported sysctls (by - the container runtime) might fail to launch. Note that - this field cannot be set when spec.os.name is windows. - items: - description: Sysctl defines a kernel parameter to be - set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: object - type: array - windowsOptions: - description: The Windows specific settings applied to - all containers. If unspecified, the options within a - container's SecurityContext will be used. If set in - both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence. Note - that this field cannot be set when spec.os.name is linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA - admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec - named by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of - the GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container - should be run as a 'Host Process' container. This - field is alpha-level and will only be honored by - components that enable the WindowsHostProcessContainers - feature flag. Setting this field without the feature - flag will result in errors when validating the Pod. - All of a Pod's containers must have the same effective - HostProcess value (it is not allowed to have a mix - of HostProcess containers and non-HostProcess containers). In - addition, if HostProcess is true then HostNetwork - must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set - in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - reportsSecurityContext: - description: Security Context to apply to the Cryostat report - generator container. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether - a process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag - will be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN Note that this field cannot be - set when spec.os.name is windows.' - type: boolean - capabilities: - description: The capabilities to add/drop when running - containers. Defaults to the default set of capabilities - granted by the container runtime. Note that this field - cannot be set when spec.os.name is windows. - properties: - add: - description: Added capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - drop: - description: Removed capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - type: object - privileged: - description: Run container in privileged mode. Processes - in privileged containers are essentially equivalent - to root on the host. Defaults to false. Note that this - field cannot be set when spec.os.name is windows. - type: boolean - procMount: - description: procMount denotes the type of proc mount - to use for the containers. The default is DefaultProcMount - which uses the container runtime defaults for readonly - paths and masked paths. This requires the ProcMountType - feature flag to be enabled. Note that this field cannot - be set when spec.os.name is windows. - type: string - readOnlyRootFilesystem: - description: Whether this container has a read-only root - filesystem. Default is false. Note that this field cannot - be set when spec.os.name is windows. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be - set in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set - when spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as - a non-root user. If true, the Kubelet will validate - the image at runtime to ensure that it does not run - as UID 0 (root) and fail to start the container if it - does. If unset or false, no such validation will be - performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata - if unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - Note that this field cannot be set when spec.os.name - is windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to the - container. If unspecified, the container runtime will - allocate a random SELinux context for each container. May - also be set in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set - when spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by this container. - If seccomp options are provided at both the pod & container - level, the container options override the pod options. - Note that this field cannot be set when spec.os.name - is windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile - defined in a file on the node should be used. The - profile must be preconfigured on the node to work. - Must be a descending path, relative to the kubelet's - configured seccomp profile location. Must only be - set if type is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp - profile will be applied. Valid options are: \n Localhost - - a profile defined in a file on the node should - be used. RuntimeDefault - the container runtime - default profile should be used. Unconfined - no - profile should be applied." - type: string - required: - - type - type: object - windowsOptions: - description: The Windows specific settings applied to - all containers. If unspecified, the options from the - PodSecurityContext will be used. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set - when spec.os.name is linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA - admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec - named by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of - the GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container - should be run as a 'Host Process' container. This - field is alpha-level and will only be honored by - components that enable the WindowsHostProcessContainers - feature flag. Setting this field without the feature - flag will result in errors when validating the Pod. - All of a Pod's containers must have the same effective - HostProcess value (it is not allowed to have a mix - of HostProcess containers and non-HostProcess containers). In - addition, if HostProcess is true then HostNetwork - must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set - in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - type: object - subProcessMaxHeapSize: - description: When zero report sidecar replicas are requested, - SubProcessMaxHeapSize configures the maximum heap size of the - basic subprocess report generator in MiB. The default heap size - is `200` (MiB). - format: int32 - type: integer - type: object - resources: - description: Resource requirements for the Cryostat deployment. - properties: - coreResources: - description: Resource requirements for the Cryostat application. - If specifying a memory limit, at least 768MiB is recommended. - properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - dataSourceResources: - description: Resource requirements for the JFR Data Source container. - properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - grafanaResources: - description: Resource requirements for the Grafana container. - properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - type: object - schedulingOptions: - description: Options to configure scheduling for the Cryostat deployment - properties: - affinity: - description: Affinity rules for scheduling Cryostat pods. - properties: - nodeAffinity: - description: 'Node affinity scheduling rules for a Cryostat - pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods - to nodes that satisfy the affinity expressions specified - by this field, but it may choose a node that violates - one or more of the expressions. The node that is most - preferred is the one with the greatest sum of weights, - i.e. for each node that meets all of the scheduling - requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating - through the elements of this field and adding "weight" - to the sum if the node matches the corresponding matchExpressions; - the node(s) with the highest sum are the most preferred. - items: - description: An empty preferred scheduling term matches - all objects with implicit weight 0 (i.e. it's a no-op). - A null preferred scheduling term matches no objects - (i.e. is also a no-op). - properties: - preference: - description: A node selector term, associated with - the corresponding weight. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - weight: - description: Weight associated with matching the - corresponding nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by - this field are not met at scheduling time, the pod will - not be scheduled onto the node. If the affinity requirements - specified by this field cease to be met at some point - during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from - its node. - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. - The terms are ORed. - items: - description: A null or empty node selector term - matches no objects. The requirements of them are - ANDed. The TopologySelectorTerm type implements - a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: A list of node selector requirements - by node's labels. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchFields: - description: A list of node selector requirements - by node's fields. - items: - description: A node selector requirement is - a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: The label key that the selector - applies to. - type: string - operator: - description: Represents a key's relationship - to a set of values. Valid operators - are In, NotIn, Exists, DoesNotExist. - Gt, and Lt. - type: string - values: - description: An array of string values. - If the operator is In or NotIn, the - values array must be non-empty. If the - operator is Exists or DoesNotExist, - the values array must be empty. If the - operator is Gt or Lt, the values array - must have a single element, which will - be interpreted as an integer. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - type: object - type: array - required: - - nodeSelectorTerms - type: object - type: object - podAffinity: - description: 'Pod affinity scheduling rules for a Cryostat - pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods - to nodes that satisfy the affinity expressions specified - by this field, but it may choose a node that violates - one or more of the expressions. The node that is most - preferred is the one with the greatest sum of weights, - i.e. for each node that meets all of the scheduling - requirements (resource request, requiredDuringScheduling - affinity expressions, etc.), compute a sum by iterating - through the elements of this field and adding "weight" - to the sum if the node has pods which matches the corresponding - podAffinityTerm; the node(s) with the highest sum are - the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by - this field and the ones listed in the namespaces - field. null selector and null or empty namespaces - list means "this pod's namespace". An empty - selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. - The term is applied to the union of the namespaces - listed in this field and the ones selected - by namespaceSelector. null or empty namespaces - list and null namespaceSelector means "this - pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the - pods matching the labelSelector in the specified - namespaces, where co-located is defined as - running on a node whose value of the label - with key topologyKey matches that of any node - on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching the - corresponding podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by - this field are not met at scheduling time, the pod will - not be scheduled onto the node. If the affinity requirements - specified by this field cease to be met at some point - during pod execution (e.g. due to a pod label update), - the system may or may not try to eventually evict the - pod from its node. When there are multiple elements, - the lists of nodes corresponding to each podAffinityTerm - are intersected, i.e. all terms must be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located - is defined as running on a node whose value of the - label with key matches that of any node - on which a pod of the set of pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by this - field and the ones listed in the namespaces field. - null selector and null or empty namespaces list - means "this pod's namespace". An empty selector - ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. The - term is applied to the union of the namespaces - listed in this field and the ones selected by - namespaceSelector. null or empty namespaces list - and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey - matches that of any node on which any of the selected - pods is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - podAntiAffinity: - description: 'Pod anti-affinity scheduling rules for a Cryostat - pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods - to nodes that satisfy the anti-affinity expressions - specified by this field, but it may choose a node that - violates one or more of the expressions. The node that - is most preferred is the one with the greatest sum of - weights, i.e. for each node that meets all of the scheduling - requirements (resource request, requiredDuringScheduling - anti-affinity expressions, etc.), compute a sum by iterating - through the elements of this field and adding "weight" - to the sum if the node has pods which matches the corresponding - podAffinityTerm; the node(s) with the highest sum are - the most preferred. - items: - description: The weights of all of the matched WeightedPodAffinityTerm - fields are added per-node to find the most preferred - node(s) - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated - with the corresponding weight. - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by - this field and the ones listed in the namespaces - field. null selector and null or empty namespaces - list means "this pod's namespace". An empty - selector ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. - The term is applied to the union of the namespaces - listed in this field and the ones selected - by namespaceSelector. null or empty namespaces - list and null namespaceSelector means "this - pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the - pods matching the labelSelector in the specified - namespaces, where co-located is defined as - running on a node whose value of the label - with key topologyKey matches that of any node - on which any of the selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: weight associated with matching the - corresponding podAffinityTerm, in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified - by this field are not met at scheduling time, the pod - will not be scheduled onto the node. If the anti-affinity - requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod - label update), the system may or may not try to eventually - evict the pod from its node. When there are multiple - elements, the lists of nodes corresponding to each podAffinityTerm - are intersected, i.e. all terms must be satisfied. - items: - description: Defines a set of pods (namely those matching - the labelSelector relative to the given namespace(s)) - that this pod should be co-located (affinity) or not - co-located (anti-affinity) with, where co-located - is defined as running on a node whose value of the - label with key matches that of any node - on which a pod of the set of pods is running - properties: - labelSelector: - description: A label query over a set of resources, - in this case pods. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - namespaceSelector: - description: A label query over the set of namespaces - that the term applies to. The term is applied - to the union of the namespaces selected by this - field and the ones listed in the namespaces field. - null selector and null or empty namespaces list - means "this pod's namespace". An empty selector - ({}) matches all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - namespaces: - description: namespaces specifies a static list - of namespace names that the term applies to. The - term is applied to the union of the namespaces - listed in this field and the ones selected by - namespaceSelector. null or empty namespaces list - and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - topologyKey: - description: This pod should be co-located (affinity) - or not co-located (anti-affinity) with the pods - matching the labelSelector in the specified namespaces, - where co-located is defined as running on a node - whose value of the label with key topologyKey - matches that of any node on which any of the selected - pods is running. Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - type: array - type: object - type: object - nodeSelector: - additionalProperties: - type: string - description: 'Label selector used to schedule a Cryostat pod to - a node. See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' - type: object - tolerations: - description: 'Tolerations to allow scheduling of Cryostat pods - to tainted nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' - items: - description: The pod this Toleration is attached to tolerates - any taint that matches the triple using - the matching operator . - properties: - effect: - description: Effect indicates the taint effect to match. - Empty means match all taint effects. When specified, allowed - values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match - all values and all keys. - type: string - operator: - description: Operator represents a key's relationship to - the value. Valid operators are Exists and Equal. Defaults - to Equal. Exists is equivalent to wildcard for value, - so that a pod can tolerate all taints of a particular - category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of - time the toleration (which must be of effect NoExecute, - otherwise this field is ignored) tolerates the taint. - By default, it is not set, which means tolerate the taint - forever (do not evict). Zero and negative values will - be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. - type: string - type: object - type: array - type: object - securityOptions: - description: Options to configure the Security Contexts for the Cryostat - application. - properties: - coreSecurityContext: - description: Security Context to apply to the Cryostat application - container. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN Note that this field cannot be set - when spec.os.name is windows.' - type: boolean - capabilities: - description: The capabilities to add/drop when running containers. - Defaults to the default set of capabilities granted by the - container runtime. Note that this field cannot be set when - spec.os.name is windows. - properties: - add: - description: Added capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - drop: - description: Removed capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - type: object - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. Note that this field cannot - be set when spec.os.name is windows. - type: boolean - procMount: - description: procMount denotes the type of proc mount to use - for the containers. The default is DefaultProcMount which - uses the container runtime defaults for readonly paths and - masked paths. This requires the ProcMountType feature flag - to be enabled. Note that this field cannot be set when spec.os.name - is windows. - type: string - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. Note that this field cannot be set when - spec.os.name is windows. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. Note - that this field cannot be set when spec.os.name is windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to the container. - If unspecified, the container runtime will allocate a random - SELinux context for each container. May also be set in - PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by this container. - If seccomp options are provided at both the pod & container - level, the container options override the pod options. Note - that this field cannot be set when spec.os.name is windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile defined - in a file on the node should be used. The profile must - be preconfigured on the node to work. Must be a descending - path, relative to the kubelet's configured seccomp profile - location. Must only be set if type is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp profile - will be applied. Valid options are: \n Localhost - a - profile defined in a file on the node should be used. - RuntimeDefault - the container runtime default profile - should be used. Unconfined - no profile should be applied." - type: string - required: - - type - type: object - windowsOptions: - description: The Windows specific settings applied to all - containers. If unspecified, the options from the PodSecurityContext - will be used. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - Note that this field cannot be set when spec.os.name is - linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named - by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the - GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container should - be run as a 'Host Process' container. This field is - alpha-level and will only be honored by components that - enable the WindowsHostProcessContainers feature flag. - Setting this field without the feature flag will result - in errors when validating the Pod. All of a Pod's containers - must have the same effective HostProcess value (it is - not allowed to have a mix of HostProcess containers - and non-HostProcess containers). In addition, if HostProcess - is true then HostNetwork must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set in - PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - dataSourceSecurityContext: - description: Security Context to apply to the JFR Data Source - container. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN Note that this field cannot be set - when spec.os.name is windows.' - type: boolean - capabilities: - description: The capabilities to add/drop when running containers. - Defaults to the default set of capabilities granted by the - container runtime. Note that this field cannot be set when - spec.os.name is windows. - properties: - add: - description: Added capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - drop: - description: Removed capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - type: object - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. Note that this field cannot - be set when spec.os.name is windows. - type: boolean - procMount: - description: procMount denotes the type of proc mount to use - for the containers. The default is DefaultProcMount which - uses the container runtime defaults for readonly paths and - masked paths. This requires the ProcMountType feature flag - to be enabled. Note that this field cannot be set when spec.os.name - is windows. - type: string - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. Note that this field cannot be set when - spec.os.name is windows. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. Note - that this field cannot be set when spec.os.name is windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to the container. - If unspecified, the container runtime will allocate a random - SELinux context for each container. May also be set in - PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by this container. - If seccomp options are provided at both the pod & container - level, the container options override the pod options. Note - that this field cannot be set when spec.os.name is windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile defined - in a file on the node should be used. The profile must - be preconfigured on the node to work. Must be a descending - path, relative to the kubelet's configured seccomp profile - location. Must only be set if type is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp profile - will be applied. Valid options are: \n Localhost - a - profile defined in a file on the node should be used. - RuntimeDefault - the container runtime default profile - should be used. Unconfined - no profile should be applied." - type: string - required: - - type - type: object - windowsOptions: - description: The Windows specific settings applied to all - containers. If unspecified, the options from the PodSecurityContext - will be used. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - Note that this field cannot be set when spec.os.name is - linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named - by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the - GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container should - be run as a 'Host Process' container. This field is - alpha-level and will only be honored by components that - enable the WindowsHostProcessContainers feature flag. - Setting this field without the feature flag will result - in errors when validating the Pod. All of a Pod's containers - must have the same effective HostProcess value (it is - not allowed to have a mix of HostProcess containers - and non-HostProcess containers). In addition, if HostProcess - is true then HostNetwork must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set in - PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - grafanaSecurityContext: - description: Security Context to apply to the Grafana container. - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether a - process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag will - be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN Note that this field cannot be set - when spec.os.name is windows.' - type: boolean - capabilities: - description: The capabilities to add/drop when running containers. - Defaults to the default set of capabilities granted by the - container runtime. Note that this field cannot be set when - spec.os.name is windows. - properties: - add: - description: Added capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - drop: - description: Removed capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - type: object - privileged: - description: Run container in privileged mode. Processes in - privileged containers are essentially equivalent to root - on the host. Defaults to false. Note that this field cannot - be set when spec.os.name is windows. - type: boolean - procMount: - description: procMount denotes the type of proc mount to use - for the containers. The default is DefaultProcMount which - uses the container runtime defaults for readonly paths and - masked paths. This requires the ProcMountType feature flag - to be enabled. Note that this field cannot be set when spec.os.name - is windows. - type: string - readOnlyRootFilesystem: - description: Whether this container has a read-only root filesystem. - Default is false. Note that this field cannot be set when - spec.os.name is windows. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. Note - that this field cannot be set when spec.os.name is windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to the container. - If unspecified, the container runtime will allocate a random - SELinux context for each container. May also be set in - PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by this container. - If seccomp options are provided at both the pod & container - level, the container options override the pod options. Note - that this field cannot be set when spec.os.name is windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile defined - in a file on the node should be used. The profile must - be preconfigured on the node to work. Must be a descending - path, relative to the kubelet's configured seccomp profile - location. Must only be set if type is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp profile - will be applied. Valid options are: \n Localhost - a - profile defined in a file on the node should be used. - RuntimeDefault - the container runtime default profile - should be used. Unconfined - no profile should be applied." - type: string - required: - - type - type: object - windowsOptions: - description: The Windows specific settings applied to all - containers. If unspecified, the options from the PodSecurityContext - will be used. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - Note that this field cannot be set when spec.os.name is - linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named - by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the - GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container should - be run as a 'Host Process' container. This field is - alpha-level and will only be honored by components that - enable the WindowsHostProcessContainers feature flag. - Setting this field without the feature flag will result - in errors when validating the Pod. All of a Pod's containers - must have the same effective HostProcess value (it is - not allowed to have a mix of HostProcess containers - and non-HostProcess containers). In addition, if HostProcess - is true then HostNetwork must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set in - PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - podSecurityContext: - description: Security Context to apply to the Cryostat pod. - properties: - fsGroup: - description: "A special supplemental group that applies to - all containers in a pod. Some volume types allow the Kubelet - to change the ownership of that volume to be owned by the - pod: \n 1. The owning GID will be the FSGroup 2. The setgid - bit is set (new files created in the volume will be owned - by FSGroup) 3. The permission bits are OR'd with rw-rw---- - \n If unset, the Kubelet will not modify the ownership and - permissions of any volume. Note that this field cannot be - set when spec.os.name is windows." - format: int64 - type: integer - fsGroupChangePolicy: - description: 'fsGroupChangePolicy defines behavior of changing - ownership and permission of the volume before being exposed - inside Pod. This field will only apply to volume types which - support fsGroup based ownership(and permissions). It will - have no effect on ephemeral volume types such as: secret, - configmaps and emptydir. Valid values are "OnRootMismatch" - and "Always". If not specified, "Always" is used. Note that - this field cannot be set when spec.os.name is windows.' - type: string - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set - in SecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. Note that this field - cannot be set when spec.os.name is windows. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail - to start the container if it does. If unset or false, no - such validation will be performed. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the - value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if - unspecified. May also be set in SecurityContext. If set - in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - Note that this field cannot be set when spec.os.name is - windows. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to all containers. - If unspecified, the container runtime will allocate a random - SELinux context for each container. May also be set in - SecurityContext. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence - for that container. Note that this field cannot be set when - spec.os.name is windows. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - seccompProfile: - description: The seccomp options to use by the containers - in this pod. Note that this field cannot be set when spec.os.name - is windows. - properties: - localhostProfile: - description: localhostProfile indicates a profile defined - in a file on the node should be used. The profile must - be preconfigured on the node to work. Must be a descending - path, relative to the kubelet's configured seccomp profile - location. Must only be set if type is "Localhost". - type: string - type: - description: "type indicates which kind of seccomp profile - will be applied. Valid options are: \n Localhost - a - profile defined in a file on the node should be used. - RuntimeDefault - the container runtime default profile - should be used. Unconfined - no profile should be applied." - type: string - required: - - type - type: object - supplementalGroups: - description: A list of groups applied to the first process - run in each container, in addition to the container's primary - GID. If unspecified, no groups will be added to any container. - Note that this field cannot be set when spec.os.name is - windows. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used - for the pod. Pods with unsupported sysctls (by the container - runtime) might fail to launch. Note that this field cannot - be set when spec.os.name is windows. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: object - type: array - windowsOptions: - description: The Windows specific settings applied to all - containers. If unspecified, the options within a container's - SecurityContext will be used. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. Note that this field cannot be set when - spec.os.name is linux. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named - by the GMSACredentialSpecName field. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the - GMSA credential spec to use. - type: string - hostProcess: - description: HostProcess determines if a container should - be run as a 'Host Process' container. This field is - alpha-level and will only be honored by components that - enable the WindowsHostProcessContainers feature flag. - Setting this field without the feature flag will result - in errors when validating the Pod. All of a Pod's containers - must have the same effective HostProcess value (it is - not allowed to have a mix of HostProcess containers - and non-HostProcess containers). In addition, if HostProcess - is true then HostNetwork must also be set to true. - type: boolean - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set in - PodSecurityContext. If set in both SecurityContext and - PodSecurityContext, the value specified in SecurityContext - takes precedence. - type: string - type: object - type: object - type: object - serviceOptions: - description: Options to customize the services created for the Cryostat - application and Grafana dashboard. - properties: - coreConfig: - description: Specification for the service responsible for the - Cryostat application. - properties: - annotations: - additionalProperties: - type: string - description: Annotations to add to the service during its - creation. - type: object - httpPort: - description: HTTP port number for the Cryostat application - service. Defaults to 8181. - format: int32 - type: integer - jmxPort: - description: Remote JMX port number for the Cryostat application - service. Defaults to 9091. - format: int32 - type: integer - labels: - additionalProperties: - type: string - description: Labels to add to the service during its creation. - The labels with keys "app" and "component" are reserved - for use by the operator. - type: object - serviceType: - description: Type of service to create. Defaults to "ClusterIP". - type: string - type: object - grafanaConfig: - description: Specification for the service responsible for the - Cryostat Grafana dashboard. - properties: - annotations: - additionalProperties: - type: string - description: Annotations to add to the service during its - creation. - type: object - httpPort: - description: HTTP port number for the Grafana dashboard service. - Defaults to 3000. - format: int32 - type: integer - labels: - additionalProperties: - type: string - description: Labels to add to the service during its creation. - The labels with keys "app" and "component" are reserved - for use by the operator. - type: object - serviceType: - description: Type of service to create. Defaults to "ClusterIP". - type: string - type: object - reportsConfig: - description: Specification for the service responsible for the - cryostat-reports sidecars. - properties: - annotations: - additionalProperties: - type: string - description: Annotations to add to the service during its - creation. - type: object - httpPort: - description: HTTP port number for the cryostat-reports service. - Defaults to 10000. - format: int32 - type: integer - labels: - additionalProperties: - type: string - description: Labels to add to the service during its creation. - The labels with keys "app" and "component" are reserved - for use by the operator. - type: object - serviceType: - description: Type of service to create. Defaults to "ClusterIP". - type: string - type: object - type: object - storageOptions: - description: Options to customize the storage for Flight Recordings - and Templates. - properties: - emptyDir: - description: Configuration for an EmptyDir to be created by the - operator instead of a PVC. - properties: - enabled: - description: When enabled, Cryostat will use EmptyDir volumes - instead of a Persistent Volume Claim. Any PVC configurations - will be ignored. - type: boolean - medium: - description: Unless specified, the emptyDir volume will be - mounted on the same storage medium backing the node. Setting - this field to "Memory" will mount the emptyDir on a tmpfs - (RAM-backed filesystem). - type: string - sizeLimit: - description: The maximum memory limit for the emptyDir. Default - is unbounded. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - type: string - type: object - pvc: - description: Configuration for the Persistent Volume Claim to - be created by the operator. - properties: - annotations: - additionalProperties: - type: string - description: Annotations to add to the Persistent Volume Claim - during its creation. - type: object - labels: - additionalProperties: - type: string - description: Labels to add to the Persistent Volume Claim - during its creation. The label with key "app" is reserved - for use by the operator. - type: object - spec: - description: Spec for a Persistent Volume Claim, whose options - will override the defaults used by the operator. Unless - overriden, the PVC will be created with the default Storage - Class and 500MiB of storage. Once the operator has created - the PVC, changes to this field have no effect. - properties: - accessModes: - description: 'accessModes contains the desired access - modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' - items: - type: string - type: array - dataSource: - description: 'dataSource field can be used to specify - either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) If the provisioner - or an external controller can support the specified - data source, it will create a new volume based on the - contents of the specified data source. If the AnyVolumeDataSource - feature gate is enabled, this field will always have - the same contents as the DataSourceRef field.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource being referenced - type: string - name: - description: Name is the name of resource being referenced - type: string - required: - - kind - - name - type: object - dataSourceRef: - description: 'dataSourceRef specifies the object from - which to populate the volume with data, if a non-empty - volume is desired. This may be any local object from - a non-empty API group (non core object) or a PersistentVolumeClaim - object. When this field is specified, volume binding - will only succeed if the type of the specified object - matches some installed volume populator or dynamic provisioner. - This field will replace the functionality of the DataSource - field and as such if both fields are non-empty, they - must have the same value. For backwards compatibility, - both fields (DataSource and DataSourceRef) will be set - to the same value automatically if one of them is empty - and the other is non-empty. There are two important - differences between DataSource and DataSourceRef: * - While DataSource only allows two specific types of objects, - DataSourceRef allows any non-core object, as well as - PersistentVolumeClaim objects. * While DataSource ignores - disallowed values (dropping them), DataSourceRef preserves - all values, and generates an error if a disallowed value - is specified. (Beta) Using this field requires the AnyVolumeDataSource - feature gate to be enabled.' - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource being referenced - type: string - name: - description: Name is the name of resource being referenced - type: string - required: - - kind - - name - type: object - resources: - description: 'resources represents the minimum resources - the volume should have. If RecoverVolumeExpansionFailure - feature is enabled users are allowed to specify resource - requirements that are lower than previous value but - must still be higher than capacity recorded in the status - field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' - properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount - of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount - of compute resources required. If Requests is omitted - for a container, it defaults to Limits if that is - explicitly specified, otherwise to an implementation-defined - value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - selector: - description: selector is a label query over volumes to - consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values - array must be non-empty. If the operator is - Exists or DoesNotExist, the values array must - be empty. This array is replaced during a - strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - type: object - storageClassName: - description: 'storageClassName is the name of the StorageClass - required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' - type: string - volumeMode: - description: volumeMode defines what type of volume is - required by the claim. Value of Filesystem is implied - when not included in claim spec. - type: string - volumeName: - description: volumeName is the binding reference to the - PersistentVolume backing this claim. - type: string - type: object - type: object - type: object - targetDiscoveryOptions: - description: Options to configure the Cryostat application's target - discovery mechanisms. - properties: - builtInDiscoveryDisabled: - description: When true, the Cryostat application will disable - the built-in discovery mechanisms. Defaults to false - type: boolean - type: object - trustedCertSecrets: - description: List of TLS certificates to trust when connecting to - targets. - items: - properties: - certificateKey: - description: Key within secret containing the certificate. - type: string - secretName: - description: Name of secret in the local namespace. - type: string - required: - - secretName - type: object - type: array - required: - - minimal - type: object - status: - description: CryostatStatus defines the observed state of Cryostat. - properties: - applicationUrl: - description: Address of the deployed Cryostat web application. - type: string - conditions: - description: Conditions of the components managed by the Cryostat - Operator. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a foo's - current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - grafanaSecret: - description: Name of the Secret containing the generated Grafana credentials. - type: string - required: - - applicationUrl - type: object - type: object - served: true - storage: false diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 415f793ac..15c105823 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -51,6 +51,26 @@ rules: verbs: - delete - list +- apiGroups: + - operator.cryostat.io + resources: + - clustercryostats + verbs: + - '*' +- apiGroups: + - operator.cryostat.io + resources: + - clustercryostats/finalizers + verbs: + - update +- apiGroups: + - operator.cryostat.io + resources: + - clustercryostats/status + verbs: + - get + - patch + - update - apiGroups: - rbac.authorization.k8s.io resources: diff --git a/internal/controllers/model/instance.go b/internal/controllers/model/instance.go index 700c548cf..2af4089fb 100644 --- a/internal/controllers/model/instance.go +++ b/internal/controllers/model/instance.go @@ -66,10 +66,15 @@ func FromCryostat(cr *operatorv1beta1.Cryostat) *CryostatInstance { } func FromClusterCryostat(cr *operatorv1beta1.ClusterCryostat) *CryostatInstance { + // If target namespaces aren't explicitly listed, use the install namespace + targetNamespaces := cr.Spec.TargetNamespaces + if len(targetNamespaces) == 0 { + targetNamespaces = []string{cr.Spec.InstallNamespace} + } return &CryostatInstance{ Name: cr.Name, InstallNamespace: cr.Spec.InstallNamespace, - TargetNamespaces: cr.Spec.TargetNamespaces, + TargetNamespaces: targetNamespaces, Spec: &cr.Spec.CryostatSpec, Status: &cr.Status.CryostatStatus, diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index bdcedf94e..3f9755782 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -68,6 +68,7 @@ import ( operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "github.com/cryostatio/cryostat-operator/internal/controllers" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" "github.com/cryostatio/cryostat-operator/internal/test" "sigs.k8s.io/controller-runtime/pkg/log/zap" ) @@ -215,7 +216,7 @@ var _ = Describe("CryostatController", func() { } Context("successfully creates required resources", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) expectSuccessful() @@ -225,7 +226,7 @@ var _ = Describe("CryostatController", func() { BeforeEach(func() { for _, ns := range namespaces { t.Namespace = ns - t.objs = append(t.objs, t.NewNamespace(), t.NewCryostat()) + t.objs = append(t.objs, t.NewNamespace(), t.NewCryostat().Instance) } }) @@ -244,7 +245,7 @@ var _ = Describe("CryostatController", func() { BeforeEach(func() { t.Minimal = true t.GeneratedPasswords = []string{"credentials_database", "jmx", "keystore"} - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) It("should create certificates", func() { t.expectCertificates() @@ -279,7 +280,7 @@ var _ = Describe("CryostatController", func() { }) Context("after cryostat reconciled successfully", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) It("should be idempotent", func() { t.expectIdempotence() @@ -288,7 +289,7 @@ var _ = Describe("CryostatController", func() { Context("After a minimal cryostat reconciled successfully", func() { BeforeEach(func() { t.Minimal = true - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) It("should be idempotent", func() { t.expectIdempotence() @@ -303,12 +304,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing main Deployment", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldDeploy *appsv1.Deployment BeforeEach(func() { cr = t.NewCryostat() oldDeploy = t.OtherDeployment() - t.objs = append(t.objs, cr, oldDeploy) + t.objs = append(t.objs, cr.Instance, oldDeploy) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -329,7 +330,7 @@ var _ = Describe("CryostatController", func() { "app.kubernetes.io/name": "cryostat", "other": "label", })) - Expect(metav1.IsControlledBy(deploy, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(deploy, cr.Instance)).To(BeTrue()) t.checkMainPodTemplate(deploy, cr) @@ -349,12 +350,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing Service Account", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldSA *corev1.ServiceAccount BeforeEach(func() { cr = t.NewCryostat() oldSA = t.OtherServiceAccount() - t.objs = append(t.objs, cr, oldSA) + t.objs = append(t.objs, cr.Instance, oldSA) }) It("should update the Service Account", func() { t.reconcileCryostatFully() @@ -373,7 +374,7 @@ var _ = Describe("CryostatController", func() { "other": "label", })) - Expect(metav1.IsControlledBy(sa, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(sa, cr.Instance)).To(BeTrue()) Expect(sa.ImagePullSecrets).To(Equal(oldSA.ImagePullSecrets)) Expect(sa.Secrets).To(Equal(oldSA.Secrets)) @@ -381,12 +382,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing Role", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldRole *rbacv1.Role BeforeEach(func() { cr = t.NewCryostat() oldRole = t.OtherRole() - t.objs = append(t.objs, cr, oldRole) + t.objs = append(t.objs, cr.Instance, oldRole) }) It("should update the Role", func() { t.reconcileCryostatFully() @@ -395,7 +396,7 @@ var _ = Describe("CryostatController", func() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, role) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(role, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(role, cr.Instance)).To(BeTrue()) // Labels are unaffected Expect(role.Labels).To(Equal(oldRole.Labels)) @@ -406,12 +407,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing Role Binding", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldBinding *rbacv1.RoleBinding BeforeEach(func() { cr = t.NewCryostat() oldBinding = t.OtherRoleBinding() - t.objs = append(t.objs, cr, oldBinding) + t.objs = append(t.objs, cr.Instance, oldBinding) }) It("should update the Role Binding", func() { t.reconcileCryostatFully() @@ -420,7 +421,7 @@ var _ = Describe("CryostatController", func() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, binding) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(binding, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(binding, cr.Instance)).To(BeTrue()) // Labels are unaffected Expect(binding.Labels).To(Equal(oldBinding.Labels)) @@ -433,12 +434,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing Cluster Role Binding", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldBinding *rbacv1.ClusterRoleBinding BeforeEach(func() { cr = t.NewCryostat() oldBinding = t.OtherClusterRoleBinding() - t.objs = append(t.objs, cr, oldBinding) + t.objs = append(t.objs, cr.Instance, oldBinding) }) It("should update the Cluster Role Binding", func() { t.reconcileCryostatFully() @@ -460,12 +461,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing Grafana Secret", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldSecret *corev1.Secret BeforeEach(func() { cr = t.NewCryostat() oldSecret = t.OtherGrafanaSecret() - t.objs = append(t.objs, cr, oldSecret) + t.objs = append(t.objs, cr.Instance, oldSecret) }) It("should update the username but not password", func() { t.reconcileCryostatFully() @@ -474,7 +475,7 @@ var _ = Describe("CryostatController", func() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: oldSecret.Name, Namespace: t.Namespace}, secret) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(secret, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(secret, cr.Instance)).To(BeTrue()) // Username should be replaced, but not password expected := t.NewGrafanaSecret() @@ -483,12 +484,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing JMX Secret", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldSecret *corev1.Secret BeforeEach(func() { cr = t.NewCryostat() oldSecret = t.OtherJMXSecret() - t.objs = append(t.objs, cr, oldSecret) + t.objs = append(t.objs, cr.Instance, oldSecret) }) It("should update the username but not password", func() { t.reconcileCryostatFully() @@ -497,7 +498,7 @@ var _ = Describe("CryostatController", func() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: oldSecret.Name, Namespace: t.Namespace}, secret) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(secret, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(secret, cr.Instance)).To(BeTrue()) // Username should be replaced, but not password expected := t.NewJMXSecret() @@ -506,12 +507,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing Credentials Database Secret", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldSecret *corev1.Secret BeforeEach(func() { cr = t.NewCryostat() oldSecret = t.OtherCredentialsDatabaseSecret() - t.objs = append(t.objs, cr, oldSecret) + t.objs = append(t.objs, cr.Instance, oldSecret) }) It("should not update password", func() { t.reconcileCryostatFully() @@ -520,19 +521,19 @@ var _ = Describe("CryostatController", func() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: oldSecret.Name, Namespace: t.Namespace}, secret) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(secret, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(secret, cr.Instance)).To(BeTrue()) Expect(secret.StringData["CRYOSTAT_JMX_CREDENTIALS_DB_PASSWORD"]).To(Equal(oldSecret.StringData["CRYOSTAT_JMX_CREDENTIALS_DB_PASSWORD"])) }) }) Context("with existing Routes", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldCoreRoute *openshiftv1.Route var oldGrafanaRoute *openshiftv1.Route BeforeEach(func() { cr = t.NewCryostat() oldCoreRoute = t.OtherCoreRoute() oldGrafanaRoute = t.OtherGrafanaRoute() - t.objs = append(t.objs, cr, oldCoreRoute, oldGrafanaRoute) + t.objs = append(t.objs, cr.Instance, oldCoreRoute, oldGrafanaRoute) }) It("should update the Routes", func() { t.reconcileCryostatFully() @@ -545,19 +546,16 @@ var _ = Describe("CryostatController", func() { BeforeEach(func() { t.Minimal = true t.GeneratedPasswords = []string{"credentials_database", "jmx", "keystore", "grafana"} - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() - cryostat := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cryostat) - Expect(err).ToNot(HaveOccurred()) + cryostat := t.getCryostatInstance() t.Minimal = false cryostat.Spec.Minimal = false - err = t.Client.Update(context.Background(), cryostat) - Expect(err).ToNot(HaveOccurred()) + t.updateCryostatInstance(cryostat) t.reconcileCryostatFully() }) @@ -577,19 +575,16 @@ var _ = Describe("CryostatController", func() { }) Context("Switching from a non-minimal to a minimal deployment", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() - cryostat := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cryostat) - Expect(err).ToNot(HaveOccurred()) + cryostat := t.getCryostatInstance() t.Minimal = true cryostat.Spec.Minimal = true - err = t.Client.Status().Update(context.Background(), cryostat) - Expect(err).ToNot(HaveOccurred()) + t.updateCryostatInstance(cryostat) t.reconcileCryostatFully() }) @@ -618,11 +613,11 @@ var _ = Describe("CryostatController", func() { }) }) Context("with report generator service", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance BeforeEach(func() { t.ReportReplicas = 1 cr = t.NewCryostat() - t.objs = append(t.objs, cr) + t.objs = append(t.objs, cr.Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -709,21 +704,18 @@ var _ = Describe("CryostatController", func() { }) Context("Switching from 0 report sidecars to 1", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() - cryostat := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cryostat) - Expect(err).ToNot(HaveOccurred()) + cryostat := t.getCryostatInstance() t.ReportReplicas = 1 cryostat.Spec.ReportOptions = &operatorv1beta1.ReportConfiguration{ Replicas: t.ReportReplicas, } - err = t.Client.Status().Update(context.Background(), cryostat) - Expect(err).ToNot(HaveOccurred()) + t.updateCryostatInstance(cryostat) t.reconcileCryostatFully() }) @@ -736,19 +728,16 @@ var _ = Describe("CryostatController", func() { Context("Switching from 1 report sidecar to 2", func() { BeforeEach(func() { t.ReportReplicas = 1 - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() - cryostat := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cryostat) - Expect(err).ToNot(HaveOccurred()) + cryostat := t.getCryostatInstance() t.ReportReplicas = 2 cryostat.Spec.ReportOptions.Replicas = t.ReportReplicas - err = t.Client.Status().Update(context.Background(), cryostat) - Expect(err).ToNot(HaveOccurred()) + t.updateCryostatInstance(cryostat) t.reconcileCryostatFully() }) @@ -761,19 +750,16 @@ var _ = Describe("CryostatController", func() { Context("Switching from 2 report sidecars to 1", func() { BeforeEach(func() { t.ReportReplicas = 2 - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() - cryostat := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cryostat) - Expect(err).ToNot(HaveOccurred()) + cryostat := t.getCryostatInstance() t.ReportReplicas = 1 cryostat.Spec.ReportOptions.Replicas = t.ReportReplicas - err = t.Client.Status().Update(context.Background(), cryostat) - Expect(err).ToNot(HaveOccurred()) + t.updateCryostatInstance(cryostat) t.reconcileCryostatFully() }) @@ -786,20 +772,17 @@ var _ = Describe("CryostatController", func() { Context("Switching from 1 report sidecar to 0", func() { BeforeEach(func() { t.ReportReplicas = 1 - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() t.makeDeploymentAvailable("cryostat-reports") - cryostat := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cryostat) - Expect(err).ToNot(HaveOccurred()) + cryostat := t.getCryostatInstance() t.ReportReplicas = 0 cryostat.Spec.ReportOptions.Replicas = t.ReportReplicas - err = t.Client.Status().Update(context.Background(), cryostat) - Expect(err).ToNot(HaveOccurred()) + t.updateCryostatInstance(cryostat) t.reconcileCryostatFully() }) @@ -815,10 +798,10 @@ var _ = Describe("CryostatController", func() { }) }) Context("Cryostat CR has list of certificate secrets", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance BeforeEach(func() { cr = t.NewCryostatWithSecrets() - t.objs = append(t.objs, cr, t.NewTestCertSecret("testCert1"), + t.objs = append(t.objs, cr.Instance, t.NewTestCertSecret("testCert1"), t.NewTestCertSecret("testCert2")) }) It("Should add volumes and volumeMounts to deployment", func() { @@ -837,7 +820,7 @@ var _ = Describe("CryostatController", func() { }) Context("Adding a certificate to the TrustedCertSecrets list", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat(), t.NewTestCertSecret("testCert1"), + t.objs = append(t.objs, t.NewCryostat().Instance, t.NewTestCertSecret("testCert1"), t.NewTestCertSecret("testCert2")) }) JustBeforeEach(func() { @@ -845,19 +828,16 @@ var _ = Describe("CryostatController", func() { }) It("Should update the corresponding deployment", func() { // Get Cryostat CR after reconciling - cr := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cr) - Expect(err).ToNot(HaveOccurred()) + cr := t.getCryostatInstance() // Update it with new TrustedCertSecrets cr.Spec.TrustedCertSecrets = t.NewCryostatWithSecrets().Spec.TrustedCertSecrets - err = t.Client.Update(context.Background(), cr) - Expect(err).ToNot(HaveOccurred()) + t.updateCryostatInstance(cr) t.reconcileCryostatFully() deployment := &appsv1.Deployment{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) volumes := deployment.Spec.Template.Spec.Volumes @@ -871,7 +851,7 @@ var _ = Describe("CryostatController", func() { }) Context("Cryostat CR has list of event templates", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithTemplates(), t.NewTemplateConfigMap(), + t.objs = append(t.objs, t.NewCryostatWithTemplates().Instance, t.NewTemplateConfigMap(), t.NewOtherTemplateConfigMap()) }) It("Should add volumes and volumeMounts to deployment", func() { @@ -885,7 +865,7 @@ var _ = Describe("CryostatController", func() { cr := t.NewCryostatWithTemplates() certManager := false cr.Spec.EnableCertManager = &certManager - t.objs = append(t.objs, cr, t.NewTemplateConfigMap(), + t.objs = append(t.objs, cr.Instance, t.NewTemplateConfigMap(), t.NewOtherTemplateConfigMap()) }) It("Should add volumes and volumeMounts to deployment", func() { @@ -895,7 +875,7 @@ var _ = Describe("CryostatController", func() { }) Context("Adding a template to the EventTemplates list", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat(), t.NewTemplateConfigMap(), + t.objs = append(t.objs, t.NewCryostat().Instance, t.NewTemplateConfigMap(), t.NewOtherTemplateConfigMap()) }) JustBeforeEach(func() { @@ -903,14 +883,11 @@ var _ = Describe("CryostatController", func() { }) It("Should update the corresponding deployment", func() { // Get Cryostat CR after reconciling - cr := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cr) - Expect(err).ToNot(HaveOccurred()) + cr := t.getCryostatInstance() // Update it with new EventTemplates cr.Spec.EventTemplates = t.NewCryostatWithTemplates().Spec.EventTemplates - err = t.Client.Update(context.Background(), cr) - Expect(err).ToNot(HaveOccurred()) + t.updateCryostatInstance(cr) t.reconcileCryostatFully() t.checkDeploymentHasTemplates() @@ -918,7 +895,7 @@ var _ = Describe("CryostatController", func() { }) Context("with custom PVC spec overriding all defaults", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithPVCSpec()) + t.objs = append(t.objs, t.NewCryostatWithPVCSpec().Instance) }) It("should create the PVC with requested spec", func() { t.expectPVC(t.NewCustomPVC()) @@ -926,7 +903,7 @@ var _ = Describe("CryostatController", func() { }) Context("with custom PVC spec overriding some defaults", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithPVCSpecSomeDefault()) + t.objs = append(t.objs, t.NewCryostatWithPVCSpecSomeDefault().Instance) }) It("should create the PVC with requested spec", func() { t.expectPVC(t.NewCustomPVCSomeDefault()) @@ -934,7 +911,7 @@ var _ = Describe("CryostatController", func() { }) Context("with custom PVC config with no spec", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithPVCLabelsOnly()) + t.objs = append(t.objs, t.NewCryostatWithPVCLabelsOnly().Instance) }) It("should create the PVC with requested label", func() { t.expectPVC(t.NewDefaultPVCWithLabel()) @@ -944,7 +921,7 @@ var _ = Describe("CryostatController", func() { var oldPVC *corev1.PersistentVolumeClaim BeforeEach(func() { oldPVC = t.NewDefaultPVC() - t.objs = append(t.objs, t.NewCryostatWithPVCSpec(), oldPVC) + t.objs = append(t.objs, t.NewCryostatWithPVCSpec().Instance, oldPVC) }) Context("that successfully updates", func() { BeforeEach(func() { @@ -993,7 +970,7 @@ var _ = Describe("CryostatController", func() { }) Context("with custom EmptyDir config", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithDefaultEmptyDir()) + t.objs = append(t.objs, t.NewCryostatWithDefaultEmptyDir().Instance) }) It("should create the EmptyDir with default specs", func() { t.expectEmptyDir(t.NewDefaultEmptyDir()) @@ -1004,7 +981,7 @@ var _ = Describe("CryostatController", func() { }) Context("with custom EmptyDir config with requested spec", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithEmptyDirSpec()) + t.objs = append(t.objs, t.NewCryostatWithEmptyDirSpec().Instance) }) It("should create the EmptyDir with requested specs", func() { t.expectEmptyDir(t.NewEmptyDirWithSpec()) @@ -1017,7 +994,7 @@ var _ = Describe("CryostatController", func() { var mainDeploy, reportsDeploy *appsv1.Deployment BeforeEach(func() { t.ReportReplicas = 1 - t.objs = append(t.objs, t.NewCryostatWithReportsSvc()) + t.objs = append(t.objs, t.NewCryostatWithReportsSvc().Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1135,7 +1112,7 @@ var _ = Describe("CryostatController", func() { }) Context("when deleted", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1164,7 +1141,7 @@ var _ = Describe("CryostatController", func() { }) Context("on OpenShift", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1209,7 +1186,7 @@ var _ = Describe("CryostatController", func() { Context("with restricted SCC", func() { BeforeEach(func() { t.objs = []runtime.Object{ - t.NewCryostat(), t.NewNamespaceWithSCCSupGroups(), t.NewApiServer(), + t.NewCryostat().Instance, t.NewNamespaceWithSCCSupGroups(), t.NewApiServer(), } }) It("should set fsGroup to value derived from namespace", func() { @@ -1259,7 +1236,7 @@ var _ = Describe("CryostatController", func() { Context("with cert-manager disabled in CR", func() { BeforeEach(func() { t.TLS = false - t.objs = append(t.objs, t.NewCryostatCertManagerDisabled()) + t.objs = append(t.objs, t.NewCryostatCertManagerDisabled().Instance) }) It("should create deployment and set owner", func() { t.expectDeployment() @@ -1277,7 +1254,7 @@ var _ = Describe("CryostatController", func() { }) Context("with cert-manager not configured in CR", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatCertManagerUndefined()) + t.objs = append(t.objs, t.NewCryostatCertManagerUndefined().Instance) }) It("should create deployment and set owner", func() { t.expectDeployment() @@ -1299,7 +1276,7 @@ var _ = Describe("CryostatController", func() { disableTLS := true t.EnvDisableTLS = &disableTLS t.TLS = false - t.objs = append(t.objs, t.NewCryostatCertManagerUndefined()) + t.objs = append(t.objs, t.NewCryostatCertManagerUndefined().Instance) }) It("should create deployment and set owner", func() { t.expectDeployment() @@ -1322,20 +1299,17 @@ var _ = Describe("CryostatController", func() { }) Context("Disable cert-manager after being enabled", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() - cryostat := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cryostat) - Expect(err).ToNot(HaveOccurred()) + cryostat := t.getCryostatInstance() t.TLS = false certManager := false cryostat.Spec.EnableCertManager = &certManager - err = t.Client.Status().Update(context.Background(), cryostat) - Expect(err).ToNot(HaveOccurred()) + t.updateCryostatInstance(cryostat) t.reconcileCryostatFully() }) @@ -1353,20 +1327,17 @@ var _ = Describe("CryostatController", func() { Context("Enable cert-manager after being disabled", func() { BeforeEach(func() { t.TLS = false - t.objs = append(t.objs, t.NewCryostatCertManagerDisabled()) + t.objs = append(t.objs, t.NewCryostatCertManagerDisabled().Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() - cryostat := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cryostat) - Expect(err).ToNot(HaveOccurred()) + cryostat := t.getCryostatInstance() t.TLS = true certManager := true cryostat.Spec.EnableCertManager = &certManager - err = t.Client.Status().Update(context.Background(), cryostat) - Expect(err).ToNot(HaveOccurred()) + t.updateCryostatInstance(cryostat) t.reconcileCryostatFully() }) @@ -1391,7 +1362,7 @@ var _ = Describe("CryostatController", func() { }) Context("and enabled", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}} @@ -1412,7 +1383,7 @@ var _ = Describe("CryostatController", func() { Context("and disabled", func() { BeforeEach(func() { t.TLS = false - t.objs = append(t.objs, t.NewCryostatCertManagerDisabled()) + t.objs = append(t.objs, t.NewCryostatCertManagerDisabled().Instance) }) JustBeforeEach(func() { req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}} @@ -1435,45 +1406,41 @@ var _ = Describe("CryostatController", func() { }) Context("containing core config", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithCoreSvc()) + t.objs = append(t.objs, t.NewCryostatWithCoreSvc().Instance) }) - It("should created the service as described", func() { + It("should create the service as described", func() { t.checkService("cryostat", t.NewCustomizedCoreService()) }) }) Context("containing grafana config", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithGrafanaSvc()) + t.objs = append(t.objs, t.NewCryostatWithGrafanaSvc().Instance) }) - It("should created the service as described", func() { + It("should create the service as described", func() { t.checkService("cryostat-grafana", t.NewCustomizedGrafanaService()) }) }) Context("containing reports config", func() { BeforeEach(func() { t.ReportReplicas = 1 - t.objs = append(t.objs, t.NewCryostatWithReportsSvc()) + t.objs = append(t.objs, t.NewCryostatWithReportsSvc().Instance) }) - It("should created the service as described", func() { + It("should create the service as described", func() { t.checkService("cryostat-reports", t.NewCustomizedReportsService()) }) }) Context("and existing services", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { // Fetch the current Cryostat CR - namespacedName := types.NamespacedName{Name: cr.Name, Namespace: cr.Namespace} - current := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), namespacedName, current) - Expect(err).ToNot(HaveOccurred()) + current := t.getCryostatInstance() // Customize it with service options from the test specs - current.Spec = cr.Spec - err = t.Client.Update(context.Background(), current) - Expect(err).ToNot(HaveOccurred()) + *current.Spec = *cr.Spec + t.updateCryostatInstance(current) t.reconcileCryostatFully() }) @@ -1481,7 +1448,7 @@ var _ = Describe("CryostatController", func() { BeforeEach(func() { cr = t.NewCryostatWithCoreSvc() }) - It("should created the service as described", func() { + It("should create the service as described", func() { t.checkService("cryostat", t.NewCustomizedCoreService()) }) }) @@ -1489,7 +1456,7 @@ var _ = Describe("CryostatController", func() { BeforeEach(func() { cr = t.NewCryostatWithGrafanaSvc() }) - It("should created the service as described", func() { + It("should create the service as described", func() { t.checkService("cryostat-grafana", t.NewCustomizedGrafanaService()) }) }) @@ -1498,7 +1465,7 @@ var _ = Describe("CryostatController", func() { t.ReportReplicas = 1 cr = t.NewCryostatWithReportsSvc() }) - It("should created the service as described", func() { + It("should create the service as described", func() { t.checkService("cryostat-reports", t.NewCustomizedReportsService()) }) }) @@ -1510,7 +1477,7 @@ var _ = Describe("CryostatController", func() { }) Context("containing MaxWsConnections", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithWsConnectionsSpec()) + t.objs = append(t.objs, t.NewCryostatWithWsConnectionsSpec().Instance) }) It("should set max WebSocket connections", func() { t.checkCoreHasEnvironmentVariables(t.NewWsConnectionsEnv()) @@ -1518,7 +1485,7 @@ var _ = Describe("CryostatController", func() { }) Context("containing SubProcessMaxHeapSize", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithReportSubprocessHeapSpec()) + t.objs = append(t.objs, t.NewCryostatWithReportSubprocessHeapSpec().Instance) }) It("should set report subprocess max heap size", func() { t.checkCoreHasEnvironmentVariables(t.NewReportSubprocessHeapEnv()) @@ -1526,7 +1493,7 @@ var _ = Describe("CryostatController", func() { }) Context("containing JmxCacheOptions", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithJmxCacheOptionsSpec()) + t.objs = append(t.objs, t.NewCryostatWithJmxCacheOptionsSpec().Instance) }) It("should set JMX cache options", func() { t.checkCoreHasEnvironmentVariables(t.NewJmxCacheOptionsEnv()) @@ -1536,7 +1503,7 @@ var _ = Describe("CryostatController", func() { Context("with resource requirements", func() { Context("fully specified", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithResources()) + t.objs = append(t.objs, t.NewCryostatWithResources().Instance) }) It("should create expected deployment", func() { t.expectDeployment() @@ -1544,7 +1511,7 @@ var _ = Describe("CryostatController", func() { }) Context("with low limits", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithLowResourceLimit()) + t.objs = append(t.objs, t.NewCryostatWithLowResourceLimit().Instance) }) It("should create expected deployment", func() { t.expectDeployment() @@ -1557,7 +1524,7 @@ var _ = Describe("CryostatController", func() { }) Context("containing core config", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithCoreNetworkOptions()) + t.objs = append(t.objs, t.NewCryostatWithCoreNetworkOptions().Instance) }) It("should create the route as described", func() { t.checkRoute(t.NewCustomCoreRoute()) @@ -1565,7 +1532,7 @@ var _ = Describe("CryostatController", func() { }) Context("containing grafana config", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithGrafanaNetworkOptions()) + t.objs = append(t.objs, t.NewCryostatWithGrafanaNetworkOptions().Instance) }) It("should create the route as described", func() { t.checkRoute(t.NewCustomGrafanaRoute()) @@ -1574,7 +1541,7 @@ var _ = Describe("CryostatController", func() { }) Context("Cryostat CR has authorization properties", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithAuthProperties(), t.NewAuthPropertiesConfigMap(), t.NewAuthClusterRole()) + t.objs = append(t.objs, t.NewCryostatWithAuthProperties().Instance, t.NewAuthPropertiesConfigMap(), t.NewAuthClusterRole()) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1589,7 +1556,7 @@ var _ = Describe("CryostatController", func() { }) Context("containing Cryostat security options", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithSecurityOptions()) + t.objs = append(t.objs, t.NewCryostatWithSecurityOptions().Instance) }) It("should add security context as described", func() { t.checkMainDeployment() @@ -1598,7 +1565,7 @@ var _ = Describe("CryostatController", func() { Context("containing Report security options", func() { Context("with 0 report replica", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions()) + t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions().Instance) }) It("should add security context as described", func() { t.expectNoReportsDeployment() @@ -1608,7 +1575,7 @@ var _ = Describe("CryostatController", func() { BeforeEach(func() { t.ReportReplicas = 1 cr := t.NewCryostatWithReportSecurityOptions() - t.objs = append(t.objs, cr) + t.objs = append(t.objs, cr.Instance) }) It("should add security context as described", func() { t.checkReportsDeployment() @@ -1619,7 +1586,7 @@ var _ = Describe("CryostatController", func() { }) Context("with Scheduling options", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithScheduling()) + t.objs = append(t.objs, t.NewCryostatWithScheduling().Instance) }) It("should configure deployment appropriately", func() { t.expectDeployment() @@ -1628,7 +1595,7 @@ var _ = Describe("CryostatController", func() { }) Context("with built-in target discovery mechanism disabled", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithBuiltInDiscoveryDisabled()) + t.objs = append(t.objs, t.NewCryostatWithBuiltInDiscoveryDisabled().Instance) }) It("should configure deployment appropriately", func() { t.expectDeployment() @@ -1636,7 +1603,7 @@ var _ = Describe("CryostatController", func() { }) Context("with secret provided for database password", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithDatabaseSecretProvided()) + t.objs = append(t.objs, t.NewCryostatWithDatabaseSecretProvided().Instance) }) It("should configure deployment appropriately", func() { t.expectDeployment() @@ -1669,7 +1636,7 @@ var _ = Describe("CryostatController", func() { }) Context("with TLS ingress", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithIngress()) + t.objs = append(t.objs, t.NewCryostatWithIngress().Instance) }) It("should create ingresses", func() { t.expectIngresses() @@ -1688,7 +1655,7 @@ var _ = Describe("CryostatController", func() { Context("with non-TLS ingress", func() { BeforeEach(func() { t.ExternalTLS = false - t.objs = append(t.objs, t.NewCryostatWithIngress()) + t.objs = append(t.objs, t.NewCryostatWithIngress().Instance) }) It("should create ingresses", func() { t.expectIngresses() @@ -1706,7 +1673,7 @@ var _ = Describe("CryostatController", func() { }) Context("no ingress configuration is provided", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat()) + t.objs = append(t.objs, t.NewCryostat().Instance) }) It("should not create ingresses or routes", func() { t.reconcileCryostatFully() @@ -1715,41 +1682,36 @@ var _ = Describe("CryostatController", func() { }) }) Context("with existing Ingresses", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldCoreIngress *netv1.Ingress var oldGrafanaIngress *netv1.Ingress BeforeEach(func() { cr = t.NewCryostatWithIngress() oldCoreIngress = t.OtherCoreIngress() oldGrafanaIngress = t.OtherGrafanaIngress() - t.objs = append(t.objs, cr, oldCoreIngress, oldGrafanaIngress) + t.objs = append(t.objs, cr.Instance, oldCoreIngress, oldGrafanaIngress) }) It("should update the Ingresses", func() { t.expectIngresses() }) }) Context("networkConfig for one of the services is nil", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance BeforeEach(func() { cr = t.NewCryostatWithIngress() - t.objs = append(t.objs, cr) + t.objs = append(t.objs, cr.Instance) }) It("should only create specified ingresses", func() { - c := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, c) - Expect(err).ToNot(HaveOccurred()) - err = t.Client.Update(context.Background(), c) - Expect(err).ToNot(HaveOccurred()) + c := t.getCryostatInstance() + c.Spec.NetworkOptions.CoreConfig = nil + t.updateCryostatInstance(c) t.reconcileCryostatFully() expectedConfig := cr.Spec.NetworkOptions ingress := &netv1.Ingress{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, ingress) - Expect(err).ToNot(HaveOccurred()) - Expect(ingress.Annotations).To(Equal(expectedConfig.CoreConfig.Annotations)) - Expect(ingress.Labels).To(Equal(expectedConfig.CoreConfig.Labels)) - Expect(ingress.Spec).To(Equal(*expectedConfig.CoreConfig.IngressSpec)) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, ingress) + Expect(kerrors.IsNotFound(err)).To(BeTrue()) err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana", Namespace: t.Namespace}, ingress) Expect(err).ToNot(HaveOccurred()) @@ -1760,24 +1722,21 @@ var _ = Describe("CryostatController", func() { }) }) Context("ingressSpec for one of the services is nil", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance BeforeEach(func() { cr = t.NewCryostatWithIngress() - t.objs = append(t.objs, cr) + t.objs = append(t.objs, cr.Instance) }) It("should only create specified ingresses", func() { - c := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, c) - Expect(err).ToNot(HaveOccurred()) + c := t.getCryostatInstance() c.Spec.NetworkOptions.CoreConfig.IngressSpec = nil - err = t.Client.Update(context.Background(), c) - Expect(err).ToNot(HaveOccurred()) + t.updateCryostatInstance(c) t.reconcileCryostatFully() expectedConfig := cr.Spec.NetworkOptions ingress := &netv1.Ingress{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana", Namespace: t.Namespace}, ingress) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana", Namespace: t.Namespace}, ingress) Expect(err).ToNot(HaveOccurred()) Expect(ingress.Annotations).To(Equal(expectedConfig.GrafanaConfig.Annotations)) Expect(ingress.Labels).To(Equal(expectedConfig.GrafanaConfig.Labels)) @@ -1789,7 +1748,7 @@ var _ = Describe("CryostatController", func() { }) Context("Cryostat CR has authorization properties", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithAuthProperties(), t.NewAuthPropertiesConfigMap(), t.NewAuthClusterRole()) + t.objs = append(t.objs, t.NewCryostatWithAuthProperties().Instance, t.NewAuthPropertiesConfigMap(), t.NewAuthClusterRole()) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1802,7 +1761,7 @@ var _ = Describe("CryostatController", func() { BeforeEach(func() { t.ReportReplicas = 1 cr := t.NewCryostatWithIngress() - t.objs = append(t.objs, cr) + t.objs = append(t.objs, cr.Instance) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1821,7 +1780,7 @@ var _ = Describe("CryostatController", func() { }) Context("containing Cryostat security options", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithSecurityOptions()) + t.objs = append(t.objs, t.NewCryostatWithSecurityOptions().Instance) }) It("should add security context as described", func() { t.checkMainDeployment() @@ -1830,7 +1789,7 @@ var _ = Describe("CryostatController", func() { Context("containing Report security options", func() { Context("with 0 report replica", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions()) + t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions().Instance) }) It("should add security context as described", func() { t.expectNoReportsDeployment() @@ -1839,7 +1798,7 @@ var _ = Describe("CryostatController", func() { Context("with 1 report replicas", func() { BeforeEach(func() { t.ReportReplicas = 1 - t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions()) + t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions().Instance) }) It("should add security context as described", func() { t.checkReportsDeployment() @@ -1849,12 +1808,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing Service Account", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldSA *corev1.ServiceAccount BeforeEach(func() { cr = t.NewCryostat() oldSA = t.OtherServiceAccount() - t.objs = append(t.objs, cr, oldSA) + t.objs = append(t.objs, cr.Instance, oldSA) }) It("should update the Service Account", func() { t.reconcileCryostatFully() @@ -1872,7 +1831,7 @@ var _ = Describe("CryostatController", func() { "other": "label", })) - Expect(metav1.IsControlledBy(sa, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(sa, cr.Instance)).To(BeTrue()) Expect(sa.ImagePullSecrets).To(Equal(oldSA.ImagePullSecrets)) Expect(sa.Secrets).To(Equal(oldSA.Secrets)) @@ -1880,12 +1839,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing Role", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldRole *rbacv1.Role BeforeEach(func() { cr = t.NewCryostat() oldRole = t.OtherRole() - t.objs = append(t.objs, cr, oldRole) + t.objs = append(t.objs, cr.Instance, oldRole) }) It("should update the Role", func() { t.reconcileCryostatFully() @@ -1894,7 +1853,7 @@ var _ = Describe("CryostatController", func() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, role) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(role, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(role, cr.Instance)).To(BeTrue()) // Labels are unaffected Expect(role.Labels).To(Equal(oldRole.Labels)) @@ -1905,12 +1864,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing Role Binding", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldBinding *rbacv1.RoleBinding BeforeEach(func() { cr = t.NewCryostat() oldBinding = t.OtherRoleBinding() - t.objs = append(t.objs, cr, oldBinding) + t.objs = append(t.objs, cr.Instance, oldBinding) }) It("should update the Role Binding", func() { t.reconcileCryostatFully() @@ -1919,7 +1878,7 @@ var _ = Describe("CryostatController", func() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, binding) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(binding, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(binding, cr.Instance)).To(BeTrue()) // Labels are unaffected Expect(binding.Labels).To(Equal(oldBinding.Labels)) @@ -1932,12 +1891,12 @@ var _ = Describe("CryostatController", func() { }) }) Context("with an existing Cluster Role Binding", func() { - var cr *operatorv1beta1.Cryostat + var cr *model.CryostatInstance var oldBinding *rbacv1.ClusterRoleBinding BeforeEach(func() { cr = t.NewCryostat() oldBinding = t.OtherClusterRoleBinding() - t.objs = append(t.objs, cr, oldBinding) + t.objs = append(t.objs, cr.Instance, oldBinding) }) It("should update the Cluster Role Binding", func() { t.reconcileCryostatFully() @@ -1981,9 +1940,7 @@ func (t *cryostatTestInput) checkRoute(expected *openshiftv1.Route) *openshiftv1 } func (t *cryostatTestInput) checkConditionPresent(condType operatorv1beta1.CryostatConditionType, status metav1.ConditionStatus, reason string) { - cr := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cr) - Expect(err).ToNot(HaveOccurred()) + cr := t.getCryostatInstance() condition := meta.FindStatusCondition(cr.Status.Conditions, string(condType)) Expect(condition).ToNot(BeNil()) @@ -1992,9 +1949,7 @@ func (t *cryostatTestInput) checkConditionPresent(condType operatorv1beta1.Cryos } func (t *cryostatTestInput) checkConditionAbsent(condType operatorv1beta1.CryostatConditionType) { - cr := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cr) - Expect(err).ToNot(HaveOccurred()) + cr := t.getCryostatInstance() condition := meta.FindStatusCondition(cr.Status.Conditions, string(condType)) Expect(condition).To(BeNil()) @@ -2011,14 +1966,11 @@ func (t *cryostatTestInput) reconcileCryostatFully() { func (t *cryostatTestInput) reconcileDeletedCryostat() { // Simulate deletion by setting DeletionTimestamp - cr := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cr) - Expect(err).ToNot(HaveOccurred()) + cr := t.getCryostatInstance() delTime := metav1.Unix(0, 1598045501618*int64(time.Millisecond)) - cr.DeletionTimestamp = &delTime - err = t.Client.Update(context.Background(), cr) - Expect(err).ToNot(HaveOccurred()) + cr.Instance.SetDeletionTimestamp(&delTime) + t.updateCryostatInstance(cr) // Reconcile again t.reconcileCryostatFully() @@ -2036,8 +1988,7 @@ func checkMetadata(object metav1.Object, expected metav1.Object) { } func (t *cryostatTestInput) expectNoCryostat() { - instance := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, instance) + _, err := t.lookupCryostatInstance() Expect(kerrors.IsNotFound(err)).To(BeTrue()) } @@ -2149,13 +2100,11 @@ func (t *cryostatTestInput) expectNoRoutes() { } func (t *cryostatTestInput) checkIngresses() { - cr := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cr) - Expect(err).ToNot(HaveOccurred()) + cr := t.getCryostatInstance() expectedConfig := cr.Spec.NetworkOptions ingress := &netv1.Ingress{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, ingress) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, ingress) Expect(err).ToNot(HaveOccurred()) Expect(ingress.Annotations).To(Equal(expectedConfig.CoreConfig.Annotations)) Expect(ingress.Labels).To(Equal(expectedConfig.CoreConfig.Labels)) @@ -2304,32 +2253,19 @@ func (t *cryostatTestInput) expectCoreService() { } func (t *cryostatTestInput) expectStatusApplicationURL() { - instance := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, instance) - Expect(err).ToNot(HaveOccurred()) - t.reconcileCryostatFully() - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, instance) - Expect(err).ToNot(HaveOccurred()) - + instance := t.getCryostatInstance() Expect(instance.Status.ApplicationURL).To(Equal("https://cryostat.example.com")) } func (t *cryostatTestInput) expectStatusGrafanaSecretName(secretName string) { - instance := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, instance) - Expect(err).ToNot(HaveOccurred()) - t.reconcileCryostatFully() t.checkStatusGrafanaSecretName(secretName) } func (t *cryostatTestInput) checkStatusGrafanaSecretName(secretName string) { - instance := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, instance) - Expect(err).ToNot(HaveOccurred()) - + instance := t.getCryostatInstance() Expect(instance.Status.GrafanaSecret).To(Equal(secretName)) } @@ -2358,28 +2294,21 @@ func (t *cryostatTestInput) expectDeploymentHasCertSecrets() { } func (t *cryostatTestInput) expectIdempotence() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}} t.reconcileCryostatFully() - obj := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), req.NamespacedName, obj) - Expect(err).ToNot(HaveOccurred()) + obj := t.getCryostatInstance() // Reconcile again t.reconcileCryostatFully() - obj2 := &operatorv1beta1.Cryostat{} - err = t.Client.Get(context.Background(), req.NamespacedName, obj2) - Expect(err).ToNot(HaveOccurred()) + obj2 := t.getCryostatInstance() Expect(obj2.Status).To(Equal(obj.Status)) Expect(obj2.Spec).To(Equal(obj.Spec)) } func (t *cryostatTestInput) expectCryostatFinalizerPresent() { - cr := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cr) - Expect(err).ToNot(HaveOccurred()) - Expect(cr.GetFinalizers()).To(ContainElement("operator.cryostat.io/cryostat.finalizer")) + cr := t.getCryostatInstance() + Expect(cr.Instance.GetFinalizers()).To(ContainElement("operator.cryostat.io/cryostat.finalizer")) } func (t *cryostatTestInput) checkGrafanaService() { @@ -2472,9 +2401,7 @@ func (t *cryostatTestInput) checkMainDeployment() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) - cr := &operatorv1beta1.Cryostat{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cr) - Expect(err).ToNot(HaveOccurred()) + cr := t.getCryostatInstance() Expect(deployment.Name).To(Equal("cryostat")) Expect(deployment.Namespace).To(Equal(t.Namespace)) @@ -2487,7 +2414,7 @@ func (t *cryostatTestInput) checkMainDeployment() { "component": "cryostat", "app.kubernetes.io/name": "cryostat", })) - Expect(metav1.IsControlledBy(deployment, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(deployment, cr.Instance)).To(BeTrue()) Expect(deployment.Spec.Selector).To(Equal(t.NewMainDeploymentSelector())) Expect(deployment.Spec.Replicas).ToNot(BeNil()) Expect(*deployment.Spec.Replicas).To(Equal(int32(1))) @@ -2497,7 +2424,7 @@ func (t *cryostatTestInput) checkMainDeployment() { t.checkMainPodTemplate(deployment, cr) } -func (t *cryostatTestInput) checkMainPodTemplate(deployment *appsv1.Deployment, cr *operatorv1beta1.Cryostat) { +func (t *cryostatTestInput) checkMainPodTemplate(deployment *appsv1.Deployment, cr *model.CryostatInstance) { template := deployment.Spec.Template Expect(template.Name).To(Equal("cryostat")) Expect(template.Namespace).To(Equal(t.Namespace)) @@ -2564,9 +2491,7 @@ func (t *cryostatTestInput) checkReportsDeployment() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-reports", Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) - cr := &operatorv1beta1.Cryostat{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cr) - Expect(err).ToNot(HaveOccurred()) + cr := t.getCryostatInstance() Expect(deployment.Name).To(Equal("cryostat-reports")) Expect(deployment.Namespace).To(Equal(t.Namespace)) @@ -2579,7 +2504,7 @@ func (t *cryostatTestInput) checkReportsDeployment() { "component": "reports", "app.kubernetes.io/name": "cryostat-reports", })) - Expect(metav1.IsControlledBy(deployment, cr)).To(BeTrue()) + Expect(metav1.IsControlledBy(deployment, cr.Instance)).To(BeTrue()) Expect(deployment.Spec.Selector).To(Equal(t.NewReportsDeploymentSelector())) Expect(deployment.Spec.Replicas).ToNot(BeNil()) Expect(*deployment.Spec.Replicas).To(Equal(t.ReportReplicas)) @@ -2783,3 +2708,32 @@ func checkResourceRequirements(containerResource, expectedResource *corev1.Resou } } } + +func (t *cryostatTestInput) getCryostatInstance() *model.CryostatInstance { + cr, err := t.lookupCryostatInstance() + Expect(err).ToNot(HaveOccurred()) + return cr +} + +func (t *cryostatTestInput) lookupCryostatInstance() (*model.CryostatInstance, error) { + if t.ClusterScoped { + cr := &operatorv1beta1.ClusterCryostat{} + err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat"}, cr) + if err != nil { + return nil, err + } + return t.ConvertClusterToModel(cr), nil + } else { + cr := &operatorv1beta1.Cryostat{} + err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cr) + if err != nil { + return nil, err + } + return t.ConvertNamespacedToModel(cr), nil + } +} + +func (t *cryostatTestInput) updateCryostatInstance(cr *model.CryostatInstance) { + err := t.Client.Update(context.Background(), cr.Instance) + Expect(err).ToNot(HaveOccurred()) +} diff --git a/internal/test/resources.go b/internal/test/resources.go index 026460220..8db1a7311 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -41,6 +41,7 @@ import ( "fmt" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" certMeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" "github.com/onsi/gomega" @@ -62,12 +63,14 @@ import ( ) type TestResources struct { - Namespace string - Minimal bool - TLS bool - ExternalTLS bool - OpenShift bool - ReportReplicas int32 + Namespace string + Minimal bool + TLS bool + ExternalTLS bool + OpenShift bool + ReportReplicas int32 + ClusterScoped bool + TargetNamespaces []string } func NewTestScheme() *runtime.Scheme { @@ -99,7 +102,8 @@ func NewTESTRESTMapper() meta.RESTMapper { return mapper } -func (r *TestResources) NewCryostat() *operatorv1beta1.Cryostat { +// FIXME need to split this up, make private newCryostat that returns API and use either a copy or actual model.FromCryostat throughout +func (r *TestResources) NewCryostat() *model.CryostatInstance { certManager := true var reportOptions *operatorv1beta1.ReportConfiguration if r.ReportReplicas > 0 { @@ -107,20 +111,60 @@ func (r *TestResources) NewCryostat() *operatorv1beta1.Cryostat { Replicas: r.ReportReplicas, } } - return &operatorv1beta1.Cryostat{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", - Namespace: r.Namespace, - }, - Spec: operatorv1beta1.CryostatSpec{ - Minimal: r.Minimal, - EnableCertManager: &certManager, - ReportOptions: reportOptions, - }, + if r.ClusterScoped { + cr := &operatorv1beta1.ClusterCryostat{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cryostat", // FIXME name conflicts + }, + Spec: operatorv1beta1.ClusterCryostatSpec{ + InstallNamespace: r.Namespace, + CryostatSpec: operatorv1beta1.CryostatSpec{ + Minimal: r.Minimal, + EnableCertManager: &certManager, + ReportOptions: reportOptions, + }, + }, + } + return r.ConvertClusterToModel(cr) + } else { + cr := &operatorv1beta1.Cryostat{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cryostat", + Namespace: r.Namespace, + }, + Spec: operatorv1beta1.CryostatSpec{ + Minimal: r.Minimal, + EnableCertManager: &certManager, + ReportOptions: reportOptions, + }, + } + return r.ConvertNamespacedToModel(cr) + } +} + +func (r *TestResources) ConvertNamespacedToModel(cr *operatorv1beta1.Cryostat) *model.CryostatInstance { + return &model.CryostatInstance{ + Name: cr.Name, + InstallNamespace: cr.Namespace, + TargetNamespaces: []string{cr.Namespace}, + Spec: &cr.Spec, + Status: &cr.Status, + Instance: cr, + } +} + +func (r *TestResources) ConvertClusterToModel(cr *operatorv1beta1.ClusterCryostat) *model.CryostatInstance { + return &model.CryostatInstance{ + Name: cr.Name, + InstallNamespace: cr.Spec.InstallNamespace, + TargetNamespaces: cr.Spec.TargetNamespaces, + Spec: &cr.Spec.CryostatSpec, + Status: &cr.Status.CryostatStatus, + Instance: cr, } } -func (r *TestResources) NewCryostatWithSecrets() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithSecrets() *model.CryostatInstance { cr := r.NewCryostat() key := "test.crt" cr.Spec.TrustedCertSecrets = []operatorv1beta1.CertificateSecret{ @@ -135,7 +179,7 @@ func (r *TestResources) NewCryostatWithSecrets() *operatorv1beta1.Cryostat { return cr } -func (r *TestResources) NewCryostatWithTemplates() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithTemplates() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.EventTemplates = []operatorv1beta1.TemplateConfigMap{ { @@ -150,14 +194,14 @@ func (r *TestResources) NewCryostatWithTemplates() *operatorv1beta1.Cryostat { return cr } -func (r *TestResources) NewCryostatWithIngress() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithIngress() *model.CryostatInstance { cr := r.NewCryostat() networkConfig := r.newNetworkConfigurationList() cr.Spec.NetworkOptions = &networkConfig return cr } -func (r *TestResources) NewCryostatWithPVCSpec() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithPVCSpec() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ PVC: &operatorv1beta1.PersistentVolumeClaimConfig{ @@ -174,7 +218,7 @@ func (r *TestResources) NewCryostatWithPVCSpec() *operatorv1beta1.Cryostat { return cr } -func (r *TestResources) NewCryostatWithPVCSpecSomeDefault() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithPVCSpecSomeDefault() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ PVC: &operatorv1beta1.PersistentVolumeClaimConfig{ @@ -184,7 +228,7 @@ func (r *TestResources) NewCryostatWithPVCSpecSomeDefault() *operatorv1beta1.Cry return cr } -func (r *TestResources) NewCryostatWithPVCLabelsOnly() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithPVCLabelsOnly() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ PVC: &operatorv1beta1.PersistentVolumeClaimConfig{ @@ -196,7 +240,7 @@ func (r *TestResources) NewCryostatWithPVCLabelsOnly() *operatorv1beta1.Cryostat return cr } -func (r *TestResources) NewCryostatWithDefaultEmptyDir() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithDefaultEmptyDir() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ EmptyDir: &operatorv1beta1.EmptyDirConfig{ @@ -206,7 +250,7 @@ func (r *TestResources) NewCryostatWithDefaultEmptyDir() *operatorv1beta1.Cryost return cr } -func (r *TestResources) NewCryostatWithEmptyDirSpec() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithEmptyDirSpec() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ EmptyDir: &operatorv1beta1.EmptyDirConfig{ @@ -218,7 +262,7 @@ func (r *TestResources) NewCryostatWithEmptyDirSpec() *operatorv1beta1.Cryostat return cr } -func (r *TestResources) NewCryostatWithCoreSvc() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithCoreSvc() *model.CryostatInstance { svcType := corev1.ServiceTypeNodePort httpPort := int32(8080) jmxPort := int32(9095) @@ -242,7 +286,7 @@ func (r *TestResources) NewCryostatWithCoreSvc() *operatorv1beta1.Cryostat { return cr } -func (r *TestResources) NewCryostatWithGrafanaSvc() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithGrafanaSvc() *model.CryostatInstance { svcType := corev1.ServiceTypeNodePort httpPort := int32(8080) cr := r.NewCryostat() @@ -264,7 +308,7 @@ func (r *TestResources) NewCryostatWithGrafanaSvc() *operatorv1beta1.Cryostat { return cr } -func (r *TestResources) NewCryostatWithReportsSvc() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithReportsSvc() *model.CryostatInstance { svcType := corev1.ServiceTypeNodePort httpPort := int32(13161) cr := r.NewCryostat() @@ -286,7 +330,7 @@ func (r *TestResources) NewCryostatWithReportsSvc() *operatorv1beta1.Cryostat { return cr } -func (r *TestResources) NewCryostatWithCoreNetworkOptions() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithCoreNetworkOptions() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.NetworkOptions = &operatorv1beta1.NetworkConfigurationList{ CoreConfig: &operatorv1beta1.NetworkConfiguration{ @@ -297,7 +341,7 @@ func (r *TestResources) NewCryostatWithCoreNetworkOptions() *operatorv1beta1.Cry return cr } -func (r *TestResources) NewCryostatWithGrafanaNetworkOptions() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithGrafanaNetworkOptions() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.NetworkOptions = &operatorv1beta1.NetworkConfigurationList{ GrafanaConfig: &operatorv1beta1.NetworkConfiguration{ @@ -308,7 +352,7 @@ func (r *TestResources) NewCryostatWithGrafanaNetworkOptions() *operatorv1beta1. return cr } -func (r *TestResources) NewCryostatWithReportsResources() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithReportsResources() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.ReportOptions = &operatorv1beta1.ReportConfiguration{ Replicas: 1, @@ -326,7 +370,7 @@ func (r *TestResources) NewCryostatWithReportsResources() *operatorv1beta1.Cryos return cr } -func (r *TestResources) NewCryostatWithReportLowResourceLimit() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithReportLowResourceLimit() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.ReportOptions = &operatorv1beta1.ReportConfiguration{ Replicas: 1, @@ -392,13 +436,13 @@ func populateCryostatWithScheduling() *operatorv1beta1.SchedulingConfiguration { } -func (r *TestResources) NewCryostatWithScheduling() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithScheduling() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.SchedulingOptions = populateCryostatWithScheduling() return cr } -func (r *TestResources) NewCryostatWithReportsScheduling() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithReportsScheduling() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.ReportOptions = &operatorv1beta1.ReportConfiguration{ Replicas: 1, @@ -408,20 +452,20 @@ func (r *TestResources) NewCryostatWithReportsScheduling() *operatorv1beta1.Cryo return cr } -func (r *TestResources) NewCryostatCertManagerDisabled() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatCertManagerDisabled() *model.CryostatInstance { cr := r.NewCryostat() certManager := false cr.Spec.EnableCertManager = &certManager return cr } -func (r *TestResources) NewCryostatCertManagerUndefined() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatCertManagerUndefined() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.EnableCertManager = nil return cr } -func (r *TestResources) NewCryostatWithResources() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithResources() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.Resources = &operatorv1beta1.ResourceConfigList{ CoreResources: corev1.ResourceRequirements{ @@ -458,7 +502,7 @@ func (r *TestResources) NewCryostatWithResources() *operatorv1beta1.Cryostat { return cr } -func (r *TestResources) NewCryostatWithLowResourceLimit() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithLowResourceLimit() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.Resources = &operatorv1beta1.ResourceConfigList{ CoreResources: corev1.ResourceRequirements{ @@ -483,7 +527,7 @@ func (r *TestResources) NewCryostatWithLowResourceLimit() *operatorv1beta1.Cryos return cr } -func (r *TestResources) NewCryostatWithAuthProperties() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithAuthProperties() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.AuthProperties = &operatorv1beta1.AuthorizationProperties{ ConfigMapName: "authConfigMapName", @@ -493,7 +537,7 @@ func (r *TestResources) NewCryostatWithAuthProperties() *operatorv1beta1.Cryosta return cr } -func (r *TestResources) NewCryostatWithBuiltInDiscoveryDisabled() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithBuiltInDiscoveryDisabled() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.TargetDiscoveryOptions = &operatorv1beta1.TargetDiscoveryOptions{ BuiltInDiscoveryDisabled: true, @@ -514,7 +558,7 @@ func newPVCSpec(storageClass string, storageRequest string, } } -func (r *TestResources) NewCryostatWithJmxCacheOptionsSpec() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithJmxCacheOptionsSpec() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.JmxCacheOptions = &operatorv1beta1.JmxCacheOptions{ TargetCacheSize: 10, @@ -523,13 +567,13 @@ func (r *TestResources) NewCryostatWithJmxCacheOptionsSpec() *operatorv1beta1.Cr return cr } -func (r *TestResources) NewCryostatWithWsConnectionsSpec() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithWsConnectionsSpec() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.MaxWsConnections = 10 return cr } -func (r *TestResources) NewCryostatWithReportSubprocessHeapSpec() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithReportSubprocessHeapSpec() *model.CryostatInstance { cr := r.NewCryostat() if cr.Spec.ReportOptions == nil { cr.Spec.ReportOptions = &operatorv1beta1.ReportConfiguration{} @@ -538,7 +582,7 @@ func (r *TestResources) NewCryostatWithReportSubprocessHeapSpec() *operatorv1bet return cr } -func (r *TestResources) NewCryostatWithSecurityOptions() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithSecurityOptions() *model.CryostatInstance { cr := r.NewCryostat() privEscalation := true nonRoot := false @@ -577,7 +621,7 @@ func (r *TestResources) NewCryostatWithSecurityOptions() *operatorv1beta1.Cryost return cr } -func (r *TestResources) NewCryostatWithReportSecurityOptions() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithReportSecurityOptions() *model.CryostatInstance { cr := r.NewCryostat() nonRoot := true privEscalation := false @@ -605,7 +649,7 @@ func (r *TestResources) NewCryostatWithReportSecurityOptions() *operatorv1beta1. var providedDatabaseSecretName string = "credentials-database-secret" -func (r *TestResources) NewCryostatWithDatabaseSecretProvided() *operatorv1beta1.Cryostat { +func (r *TestResources) NewCryostatWithDatabaseSecretProvided() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.JmxCredentialsDatabaseOptions = &operatorv1beta1.JmxCredentialsDatabaseOptions{ DatabaseSecretName: &providedDatabaseSecretName, @@ -1975,7 +2019,7 @@ func (r *TestResources) commonDefaultSecurityContext() *corev1.SecurityContext { } } -func (r *TestResources) NewPodSecurityContext(cr *operatorv1beta1.Cryostat) *corev1.PodSecurityContext { +func (r *TestResources) NewPodSecurityContext(cr *model.CryostatInstance) *corev1.PodSecurityContext { if cr.Spec.SecurityOptions != nil && cr.Spec.SecurityOptions.PodSecurityContext != nil { return cr.Spec.SecurityOptions.PodSecurityContext } @@ -1983,35 +2027,35 @@ func (r *TestResources) NewPodSecurityContext(cr *operatorv1beta1.Cryostat) *cor return r.commonDefaultPodSecurityContext(&fsGroup) } -func (r *TestResources) NewReportPodSecurityContext(cr *operatorv1beta1.Cryostat) *corev1.PodSecurityContext { +func (r *TestResources) NewReportPodSecurityContext(cr *model.CryostatInstance) *corev1.PodSecurityContext { if cr.Spec.ReportOptions != nil && cr.Spec.ReportOptions.SecurityOptions != nil && cr.Spec.ReportOptions.SecurityOptions.PodSecurityContext != nil { return cr.Spec.ReportOptions.SecurityOptions.PodSecurityContext } return r.commonDefaultPodSecurityContext(nil) } -func (r *TestResources) NewCoreSecurityContext(cr *operatorv1beta1.Cryostat) *corev1.SecurityContext { +func (r *TestResources) NewCoreSecurityContext(cr *model.CryostatInstance) *corev1.SecurityContext { if cr.Spec.SecurityOptions != nil && cr.Spec.SecurityOptions.CoreSecurityContext != nil { return cr.Spec.SecurityOptions.CoreSecurityContext } return r.commonDefaultSecurityContext() } -func (r *TestResources) NewGrafanaSecurityContext(cr *operatorv1beta1.Cryostat) *corev1.SecurityContext { +func (r *TestResources) NewGrafanaSecurityContext(cr *model.CryostatInstance) *corev1.SecurityContext { if cr.Spec.SecurityOptions != nil && cr.Spec.SecurityOptions.GrafanaSecurityContext != nil { return cr.Spec.SecurityOptions.GrafanaSecurityContext } return r.commonDefaultSecurityContext() } -func (r *TestResources) NewDatasourceSecurityContext(cr *operatorv1beta1.Cryostat) *corev1.SecurityContext { +func (r *TestResources) NewDatasourceSecurityContext(cr *model.CryostatInstance) *corev1.SecurityContext { if cr.Spec.SecurityOptions != nil && cr.Spec.SecurityOptions.DataSourceSecurityContext != nil { return cr.Spec.SecurityOptions.DataSourceSecurityContext } return r.commonDefaultSecurityContext() } -func (r *TestResources) NewReportSecurityContext(cr *operatorv1beta1.Cryostat) *corev1.SecurityContext { +func (r *TestResources) NewReportSecurityContext(cr *model.CryostatInstance) *corev1.SecurityContext { if cr.Spec.ReportOptions != nil && cr.Spec.ReportOptions.SecurityOptions != nil && cr.Spec.ReportOptions.SecurityOptions.ReportsSecurityContext != nil { return cr.Spec.ReportOptions.SecurityOptions.ReportsSecurityContext } @@ -2586,7 +2630,7 @@ func newCoreContainerDefaultResource() *corev1.ResourceRequirements { } } -func (r *TestResources) NewCoreContainerResource(cr *operatorv1beta1.Cryostat) *corev1.ResourceRequirements { +func (r *TestResources) NewCoreContainerResource(cr *model.CryostatInstance) *corev1.ResourceRequirements { requests := newCoreContainerDefaultResource().Requests var limits corev1.ResourceList if cr.Spec.Resources != nil && cr.Spec.Resources.CoreResources.Requests != nil { @@ -2614,7 +2658,7 @@ func newDatasourceContainerDefaultResource() *corev1.ResourceRequirements { } } -func (r *TestResources) NewDatasourceContainerResource(cr *operatorv1beta1.Cryostat) *corev1.ResourceRequirements { +func (r *TestResources) NewDatasourceContainerResource(cr *model.CryostatInstance) *corev1.ResourceRequirements { requests := newDatasourceContainerDefaultResource().Requests var limits corev1.ResourceList if cr.Spec.Resources != nil && cr.Spec.Resources.DataSourceResources.Requests != nil { @@ -2642,7 +2686,7 @@ func newGrafanaContainerDefaultResource() *corev1.ResourceRequirements { } } -func (r *TestResources) NewGrafanaContainerResource(cr *operatorv1beta1.Cryostat) *corev1.ResourceRequirements { +func (r *TestResources) NewGrafanaContainerResource(cr *model.CryostatInstance) *corev1.ResourceRequirements { requests := newGrafanaContainerDefaultResource().Requests var limits corev1.ResourceList if cr.Spec.Resources != nil && cr.Spec.Resources.GrafanaResources.Requests != nil { @@ -2670,7 +2714,7 @@ func newReportContainerDefaultResource() *corev1.ResourceRequirements { } } -func (r *TestResources) NewReportContainerResource(cr *operatorv1beta1.Cryostat) *corev1.ResourceRequirements { +func (r *TestResources) NewReportContainerResource(cr *model.CryostatInstance) *corev1.ResourceRequirements { requests := newReportContainerDefaultResource().Requests var limits corev1.ResourceList From f1d79fe3d0832ad13a80160ce24a3e3175744e92 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Mon, 23 Jan 2023 17:11:48 -0500 Subject: [PATCH 03/21] Make CR name in tests configurable --- internal/controllers/rbac.go | 2 +- internal/controllers/reconciler.go | 1 - internal/controllers/reconciler_test.go | 242 ++++++++++++------------ internal/test/resources.go | 231 +++++++++++----------- 4 files changed, 237 insertions(+), 239 deletions(-) diff --git a/internal/controllers/rbac.go b/internal/controllers/rbac.go index 6d9d08692..17d19b9b5 100644 --- a/internal/controllers/rbac.go +++ b/internal/controllers/rbac.go @@ -89,7 +89,7 @@ func newServiceAccount(cr *model.CryostatInstance) *corev1.ServiceAccount { func (r *Reconciler) reconcileServiceAccount(ctx context.Context, cr *model.CryostatInstance) error { sa := newServiceAccount(cr) labels := map[string]string{ - "app": "cryostat", + "app": cr.Name, } annotations := map[string]string{} // If running on OpenShift, set the route reference as an annotation. diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index 560e749af..5acc780cc 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -79,7 +79,6 @@ type ReconcilerConfig struct { type Reconciler struct { *ReconcilerConfig - updateStatus func(context.Context, runtime.Object) // FIXME } // Name used for Finalizer that handles Cryostat deletion diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index 3f9755782..d0a4765db 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -39,7 +39,6 @@ package controllers_test import ( "context" "fmt" - "strconv" "time" certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" @@ -111,6 +110,7 @@ var _ = Describe("CryostatController", func() { GeneratedPasswords: []string{"grafana", "credentials_database", "jmx", "keystore"}, }, TestResources: &test.TestResources{ + Name: "cryostat", Namespace: "test", TLS: true, ExternalTLS: true, @@ -152,7 +152,7 @@ var _ = Describe("CryostatController", func() { }) It("should create Grafana service and set owner", func() { service := &corev1.Service{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana", Namespace: t.Namespace}, service) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana", Namespace: t.Namespace}, service) Expect(kerrors.IsNotFound(err)).To(BeTrue()) t.reconcileCryostatFully() @@ -178,7 +178,7 @@ var _ = Describe("CryostatController", func() { Context("deployment is progressing", func() { JustBeforeEach(func() { t.reconcileCryostatFully() - t.makeDeploymentProgress("cryostat") + t.makeDeploymentProgress(t.Name) }) It("should update conditions", func() { t.checkConditionPresent(operatorv1beta1.ConditionTypeMainDeploymentAvailable, metav1.ConditionFalse, @@ -189,7 +189,7 @@ var _ = Describe("CryostatController", func() { }) Context("then becomes available", func() { JustBeforeEach(func() { - t.makeDeploymentAvailable("cryostat") + t.makeDeploymentAvailable(t.Name) }) It("should update conditions", func() { t.checkConditionPresent(operatorv1beta1.ConditionTypeMainDeploymentAvailable, metav1.ConditionTrue, @@ -201,7 +201,7 @@ var _ = Describe("CryostatController", func() { }) Context("then fails to roll out", func() { JustBeforeEach(func() { - t.makeDeploymentFail("cryostat") + t.makeDeploymentFail(t.Name) }) It("should update conditions", func() { t.checkConditionPresent(operatorv1beta1.ConditionTypeMainDeploymentAvailable, metav1.ConditionFalse, @@ -316,7 +316,7 @@ var _ = Describe("CryostatController", func() { }) It("should update the Deployment", func() { deploy := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deploy) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deploy) Expect(err).ToNot(HaveOccurred()) Expect(deploy.Annotations).To(Equal(map[string]string{ @@ -324,7 +324,7 @@ var _ = Describe("CryostatController", func() { "other": "annotation", })) Expect(deploy.Labels).To(Equal(map[string]string{ - "app": "cryostat", + "app": t.Name, "kind": "cryostat", "component": "cryostat", "app.kubernetes.io/name": "cryostat", @@ -361,16 +361,16 @@ var _ = Describe("CryostatController", func() { t.reconcileCryostatFully() sa := &corev1.ServiceAccount{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, sa) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, sa) Expect(err).ToNot(HaveOccurred()) Expect(sa.Annotations).To(Equal(map[string]string{ "hello": "world", - "serviceaccounts.openshift.io/oauth-redirectreference.route": `{"metadata":{"creationTimestamp":null},"reference":{"group":"","kind":"Route","name":"cryostat"}}`, + "serviceaccounts.openshift.io/oauth-redirectreference.route": fmt.Sprintf(`{"metadata":{"creationTimestamp":null},"reference":{"group":"","kind":"Route","name":"%s"}}`, t.Name), })) Expect(sa.Labels).To(Equal(map[string]string{ - "app": "cryostat", + "app": t.Name, "other": "label", })) @@ -393,7 +393,7 @@ var _ = Describe("CryostatController", func() { t.reconcileCryostatFully() role := &rbacv1.Role{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, role) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, role) Expect(err).ToNot(HaveOccurred()) Expect(metav1.IsControlledBy(role, cr.Instance)).To(BeTrue()) @@ -418,7 +418,7 @@ var _ = Describe("CryostatController", func() { t.reconcileCryostatFully() binding := &rbacv1.RoleBinding{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, binding) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, binding) Expect(err).ToNot(HaveOccurred()) Expect(metav1.IsControlledBy(binding, cr.Instance)).To(BeTrue()) @@ -590,11 +590,11 @@ var _ = Describe("CryostatController", func() { }) It("should delete Grafana network resources", func() { service := &corev1.Service{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana", Namespace: t.Namespace}, service) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana", Namespace: t.Namespace}, service) Expect(kerrors.IsNotFound(err)).To(BeTrue()) route := &openshiftv1.Route{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana", Namespace: t.Namespace}, route) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana", Namespace: t.Namespace}, route) Expect(kerrors.IsNotFound(err)).To(BeTrue()) }) It("should delete the Grafana secret", func() { @@ -631,7 +631,7 @@ var _ = Describe("CryostatController", func() { It("should configure deployment appropriately", func() { t.checkMainDeployment() t.checkReportsDeployment() - t.checkService("cryostat-reports", t.NewReportsService()) + t.checkService(t.Name+"-reports", t.NewReportsService()) }) }) Context("with Scheduling options", func() { @@ -650,7 +650,7 @@ var _ = Describe("CryostatController", func() { It("should configure deployment appropriately", func() { t.checkMainDeployment() t.checkReportsDeployment() - t.checkService("cryostat-reports", t.NewReportsService()) + t.checkService(t.Name+"-reports", t.NewReportsService()) }) }) Context("with low limits", func() { @@ -660,13 +660,13 @@ var _ = Describe("CryostatController", func() { It("should configure deployment appropriately", func() { t.checkMainDeployment() t.checkReportsDeployment() - t.checkService("cryostat-reports", t.NewReportsService()) + t.checkService(t.Name+"-reports", t.NewReportsService()) }) }) }) Context("deployment is progressing", func() { JustBeforeEach(func() { - t.makeDeploymentProgress("cryostat-reports") + t.makeDeploymentProgress(t.Name + "-reports") }) It("should update conditions", func() { t.checkConditionPresent(operatorv1beta1.ConditionTypeReportsDeploymentAvailable, metav1.ConditionFalse, @@ -677,7 +677,7 @@ var _ = Describe("CryostatController", func() { }) Context("then becomes available", func() { JustBeforeEach(func() { - t.makeDeploymentAvailable("cryostat-reports") + t.makeDeploymentAvailable(t.Name + "-reports") }) It("should update conditions", func() { t.checkConditionPresent(operatorv1beta1.ConditionTypeReportsDeploymentAvailable, metav1.ConditionTrue, @@ -689,7 +689,7 @@ var _ = Describe("CryostatController", func() { }) Context("then fails to roll out", func() { JustBeforeEach(func() { - t.makeDeploymentFail("cryostat-reports") + t.makeDeploymentFail(t.Name + "-reports") }) It("should update conditions", func() { t.checkConditionPresent(operatorv1beta1.ConditionTypeReportsDeploymentAvailable, metav1.ConditionFalse, @@ -722,7 +722,7 @@ var _ = Describe("CryostatController", func() { It("should configure deployment appropriately", func() { t.checkMainDeployment() t.checkReportsDeployment() - t.checkService("cryostat-reports", t.NewReportsService()) + t.checkService(t.Name+"-reports", t.NewReportsService()) }) }) Context("Switching from 1 report sidecar to 2", func() { @@ -744,7 +744,7 @@ var _ = Describe("CryostatController", func() { It("should configure deployment appropriately", func() { t.checkMainDeployment() t.checkReportsDeployment() - t.checkService("cryostat-reports", t.NewReportsService()) + t.checkService(t.Name+"-reports", t.NewReportsService()) }) }) Context("Switching from 2 report sidecars to 1", func() { @@ -766,7 +766,7 @@ var _ = Describe("CryostatController", func() { It("should configure deployment appropriately", func() { t.checkMainDeployment() t.checkReportsDeployment() - t.checkService("cryostat-reports", t.NewReportsService()) + t.checkService(t.Name+"-reports", t.NewReportsService()) }) }) Context("Switching from 1 report sidecar to 0", func() { @@ -776,7 +776,7 @@ var _ = Describe("CryostatController", func() { }) JustBeforeEach(func() { t.reconcileCryostatFully() - t.makeDeploymentAvailable("cryostat-reports") + t.makeDeploymentAvailable(t.Name + "-reports") cryostat := t.getCryostatInstance() @@ -788,7 +788,7 @@ var _ = Describe("CryostatController", func() { }) It("should configure deployment appropriately", func() { t.checkMainDeployment() - t.expectNoService("cryostat-reports") + t.expectNoService(t.Name + "-reports") t.expectNoReportsDeployment() }) It("should remove conditions", func() { @@ -837,7 +837,7 @@ var _ = Describe("CryostatController", func() { t.reconcileCryostatFully() deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) volumes := deployment.Spec.Template.Spec.Volumes @@ -938,7 +938,7 @@ var _ = Describe("CryostatController", func() { expected := t.NewDefaultPVC() metav1.SetMetaDataLabel(&expected.ObjectMeta, "my", "label") metav1.SetMetaDataLabel(&expected.ObjectMeta, "another", "label") - metav1.SetMetaDataLabel(&expected.ObjectMeta, "app", "cryostat") + metav1.SetMetaDataLabel(&expected.ObjectMeta, "app", t.Name) metav1.SetMetaDataAnnotation(&expected.ObjectMeta, "my/custom", "annotation") metav1.SetMetaDataAnnotation(&expected.ObjectMeta, "another/custom", "annotation") expected.Spec.Resources.Requests[corev1.ResourceStorage] = resource.MustParse("10Gi") @@ -955,7 +955,7 @@ var _ = Describe("CryostatController", func() { t.controller.Client = t.Client // Expect an Invalid status error after reconciling - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}} + req := reconcile.Request{NamespacedName: types.NamespacedName{Name: t.Name, Namespace: t.Namespace}} _, err := t.controller.Reconcile(context.Background(), req) Expect(err).To(HaveOccurred()) Expect(kerrors.IsInvalid(err)).To(BeTrue()) @@ -999,10 +999,10 @@ var _ = Describe("CryostatController", func() { JustBeforeEach(func() { t.reconcileCryostatFully() mainDeploy = &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, mainDeploy) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, mainDeploy) Expect(err).ToNot(HaveOccurred()) reportsDeploy = &appsv1.Deployment{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-reports", Namespace: t.Namespace}, reportsDeploy) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-reports", Namespace: t.Namespace}, reportsDeploy) Expect(err).ToNot(HaveOccurred()) }) Context("for development", func() { @@ -1178,7 +1178,7 @@ var _ = Describe("CryostatController", func() { apiServer := &configv1.APIServer{} err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cluster"}, apiServer) Expect(err).ToNot(HaveOccurred()) - Expect(apiServer.Spec.AdditionalCORSAllowedOrigins).To(ContainElement("https://cryostat\\.example\\.com")) + Expect(apiServer.Spec.AdditionalCORSAllowedOrigins).To(ContainElement(fmt.Sprintf("https://%s\\.example\\.com", t.Name))) }) It("should add the finalizer", func() { t.expectCryostatFinalizerPresent() @@ -1191,7 +1191,7 @@ var _ = Describe("CryostatController", func() { }) It("should set fsGroup to value derived from namespace", func() { deploy := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deploy) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deploy) Expect(err).ToNot(HaveOccurred()) sc := deploy.Spec.Template.Spec.SecurityContext Expect(sc).ToNot(BeNil()) @@ -1365,7 +1365,7 @@ var _ = Describe("CryostatController", func() { t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}} + req := reconcile.Request{NamespacedName: types.NamespacedName{Name: t.Name, Namespace: t.Namespace}} _, err := t.controller.Reconcile(context.Background(), req) Expect(err).To(HaveOccurred()) }) @@ -1386,7 +1386,7 @@ var _ = Describe("CryostatController", func() { t.objs = append(t.objs, t.NewCryostatCertManagerDisabled().Instance) }) JustBeforeEach(func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}} + req := reconcile.Request{NamespacedName: types.NamespacedName{Name: t.Name, Namespace: t.Namespace}} _, err := t.controller.Reconcile(context.Background(), req) Expect(err).ToNot(HaveOccurred()) }) @@ -1409,7 +1409,7 @@ var _ = Describe("CryostatController", func() { t.objs = append(t.objs, t.NewCryostatWithCoreSvc().Instance) }) It("should create the service as described", func() { - t.checkService("cryostat", t.NewCustomizedCoreService()) + t.checkService(t.Name, t.NewCustomizedCoreService()) }) }) Context("containing grafana config", func() { @@ -1417,7 +1417,7 @@ var _ = Describe("CryostatController", func() { t.objs = append(t.objs, t.NewCryostatWithGrafanaSvc().Instance) }) It("should create the service as described", func() { - t.checkService("cryostat-grafana", t.NewCustomizedGrafanaService()) + t.checkService(t.Name+"-grafana", t.NewCustomizedGrafanaService()) }) }) Context("containing reports config", func() { @@ -1426,7 +1426,7 @@ var _ = Describe("CryostatController", func() { t.objs = append(t.objs, t.NewCryostatWithReportsSvc().Instance) }) It("should create the service as described", func() { - t.checkService("cryostat-reports", t.NewCustomizedReportsService()) + t.checkService(t.Name+"-reports", t.NewCustomizedReportsService()) }) }) Context("and existing services", func() { @@ -1449,7 +1449,7 @@ var _ = Describe("CryostatController", func() { cr = t.NewCryostatWithCoreSvc() }) It("should create the service as described", func() { - t.checkService("cryostat", t.NewCustomizedCoreService()) + t.checkService(t.Name, t.NewCustomizedCoreService()) }) }) Context("containing grafana config", func() { @@ -1457,7 +1457,7 @@ var _ = Describe("CryostatController", func() { cr = t.NewCryostatWithGrafanaSvc() }) It("should create the service as described", func() { - t.checkService("cryostat-grafana", t.NewCustomizedGrafanaService()) + t.checkService(t.Name+"-grafana", t.NewCustomizedGrafanaService()) }) }) Context("containing reports config", func() { @@ -1466,7 +1466,7 @@ var _ = Describe("CryostatController", func() { cr = t.NewCryostatWithReportsSvc() }) It("should create the service as described", func() { - t.checkService("cryostat-reports", t.NewCustomizedReportsService()) + t.checkService(t.Name+"-reports", t.NewCustomizedReportsService()) }) }) }) @@ -1612,7 +1612,7 @@ var _ = Describe("CryostatController", func() { t.reconcileCryostatFully() secret := &corev1.Secret{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-jmx-credentials-db", Namespace: t.Namespace}, secret) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-jmx-credentials-db", Namespace: t.Namespace}, secret) Expect(kerrors.IsNotFound(err)).To(BeTrue()) }) Context("with an existing Credentials Database Secret", func() { @@ -1623,7 +1623,7 @@ var _ = Describe("CryostatController", func() { t.reconcileCryostatFully() secret := &corev1.Secret{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-jmx-credentials-db", Namespace: t.Namespace}, secret) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-jmx-credentials-db", Namespace: t.Namespace}, secret) Expect(err).ToNot(HaveOccurred()) }) }) @@ -1710,10 +1710,10 @@ var _ = Describe("CryostatController", func() { expectedConfig := cr.Spec.NetworkOptions ingress := &netv1.Ingress{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, ingress) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, ingress) Expect(kerrors.IsNotFound(err)).To(BeTrue()) - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana", Namespace: t.Namespace}, ingress) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana", Namespace: t.Namespace}, ingress) Expect(err).ToNot(HaveOccurred()) Expect(ingress.Annotations).To(Equal(expectedConfig.GrafanaConfig.Annotations)) Expect(ingress.Labels).To(Equal(expectedConfig.GrafanaConfig.Labels)) @@ -1736,13 +1736,13 @@ var _ = Describe("CryostatController", func() { expectedConfig := cr.Spec.NetworkOptions ingress := &netv1.Ingress{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana", Namespace: t.Namespace}, ingress) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana", Namespace: t.Namespace}, ingress) Expect(err).ToNot(HaveOccurred()) Expect(ingress.Annotations).To(Equal(expectedConfig.GrafanaConfig.Annotations)) Expect(ingress.Labels).To(Equal(expectedConfig.GrafanaConfig.Labels)) Expect(ingress.Spec).To(Equal(*expectedConfig.GrafanaConfig.IngressSpec)) - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, ingress) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, ingress) Expect(kerrors.IsNotFound(err)).To(BeTrue()) }) }) @@ -1771,7 +1771,7 @@ var _ = Describe("CryostatController", func() { t.checkReportsDeployment() }) It("should create the reports service", func() { - t.checkService("cryostat-reports", t.NewReportsService()) + t.checkService(t.Name+"-reports", t.NewReportsService()) }) }) Context("with security options", func() { @@ -1819,7 +1819,7 @@ var _ = Describe("CryostatController", func() { t.reconcileCryostatFully() sa := &corev1.ServiceAccount{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, sa) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, sa) Expect(err).ToNot(HaveOccurred()) Expect(sa.Annotations).To(Equal(map[string]string{ @@ -1827,7 +1827,7 @@ var _ = Describe("CryostatController", func() { })) Expect(sa.Labels).To(Equal(map[string]string{ - "app": "cryostat", + "app": t.Name, "other": "label", })) @@ -1850,7 +1850,7 @@ var _ = Describe("CryostatController", func() { t.reconcileCryostatFully() role := &rbacv1.Role{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, role) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, role) Expect(err).ToNot(HaveOccurred()) Expect(metav1.IsControlledBy(role, cr.Instance)).To(BeTrue()) @@ -1875,7 +1875,7 @@ var _ = Describe("CryostatController", func() { t.reconcileCryostatFully() binding := &rbacv1.RoleBinding{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, binding) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, binding) Expect(err).ToNot(HaveOccurred()) Expect(metav1.IsControlledBy(binding, cr.Instance)).To(BeTrue()) @@ -1932,7 +1932,7 @@ func (t *cryostatTestInput) checkRoute(expected *openshiftv1.Route) *openshiftv1 err := t.Client.Get(context.Background(), types.NamespacedName{Name: expected.Name, Namespace: expected.Namespace}, route) Expect(err).ToNot(HaveOccurred()) - checkMetadata(route, expected) + t.checkMetadata(route, expected) Expect(route.Spec.To).To(Equal(expected.Spec.To)) Expect(route.Spec.Port).To(Equal(expected.Spec.Port)) Expect(route.Spec.TLS).To(Equal(expected.Spec.TLS)) @@ -1956,7 +1956,7 @@ func (t *cryostatTestInput) checkConditionAbsent(condType operatorv1beta1.Cryost } func (t *cryostatTestInput) reconcileCryostatFully() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}} + req := reconcile.Request{NamespacedName: types.NamespacedName{Name: t.Name, Namespace: t.Namespace}} Eventually(func() reconcile.Result { result, err := t.controller.Reconcile(context.Background(), req) Expect(err).ToNot(HaveOccurred()) @@ -1976,15 +1976,13 @@ func (t *cryostatTestInput) reconcileDeletedCryostat() { t.reconcileCryostatFully() } -func checkMetadata(object metav1.Object, expected metav1.Object) { +func (t *cryostatTestInput) checkMetadata(object metav1.Object, expected metav1.Object) { Expect(object.GetName()).To(Equal(expected.GetName())) Expect(object.GetNamespace()).To(Equal(expected.GetNamespace())) Expect(object.GetLabels()).To(Equal(expected.GetLabels())) Expect(object.GetAnnotations()).To(Equal(expected.GetAnnotations())) - ownerReferences := object.GetOwnerReferences() - Expect(ownerReferences).To(HaveLen(1)) - Expect(ownerReferences[0].Kind).To(Equal("Cryostat")) - Expect(ownerReferences[0].Name).To(Equal("cryostat")) + Expect(object.GetOwnerReferences()).To(HaveLen(1)) + Expect(metav1.IsControlledBy(object, t.getCryostatInstance().Instance)) } func (t *cryostatTestInput) expectNoCryostat() { @@ -1993,7 +1991,7 @@ func (t *cryostatTestInput) expectNoCryostat() { } func (t *cryostatTestInput) expectCertificates() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}} + req := reconcile.Request{NamespacedName: types.NamespacedName{Name: t.Name, Namespace: t.Namespace}} result, err := t.controller.Reconcile(context.Background(), req) Expect(err).ToNot(HaveOccurred()) Expect(result).To(Equal(reconcile.Result{RequeueAfter: 5 * time.Second})) @@ -2020,7 +2018,7 @@ func (t *cryostatTestInput) checkCertificates() { actual := &certv1.Certificate{} err := t.Client.Get(context.Background(), types.NamespacedName{Name: expected.Name, Namespace: expected.Namespace}, actual) Expect(err).ToNot(HaveOccurred()) - checkMetadata(actual, expected) + t.checkMetadata(actual, expected) Expect(actual.Spec).To(Equal(expected.Spec)) } // Check issuers as well @@ -2029,7 +2027,7 @@ func (t *cryostatTestInput) checkCertificates() { actual := &certv1.Issuer{} err := t.Client.Get(context.Background(), types.NamespacedName{Name: expected.Name, Namespace: expected.Namespace}, actual) Expect(err).ToNot(HaveOccurred()) - checkMetadata(actual, expected) + t.checkMetadata(actual, expected) Expect(actual.Spec).To(Equal(expected.Spec)) } // Check keystore secret @@ -2037,7 +2035,7 @@ func (t *cryostatTestInput) checkCertificates() { secret := &corev1.Secret{} err := t.Client.Get(context.Background(), types.NamespacedName{Name: expectedSecret.Name, Namespace: expectedSecret.Namespace}, secret) Expect(err).ToNot(HaveOccurred()) - checkMetadata(secret, expectedSecret) + t.checkMetadata(secret, expectedSecret) Expect(secret.StringData).To(Equal(secret.StringData)) } @@ -2045,26 +2043,26 @@ func (t *cryostatTestInput) expectRBAC() { t.reconcileCryostatFully() sa := &corev1.ServiceAccount{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, sa) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, sa) Expect(err).ToNot(HaveOccurred()) expectedSA := t.NewServiceAccount() - checkMetadata(sa, expectedSA) + t.checkMetadata(sa, expectedSA) Expect(sa.Secrets).To(Equal(expectedSA.Secrets)) Expect(sa.ImagePullSecrets).To(Equal(expectedSA.ImagePullSecrets)) Expect(sa.AutomountServiceAccountToken).To(Equal(expectedSA.AutomountServiceAccountToken)) role := &rbacv1.Role{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, role) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, role) Expect(err).ToNot(HaveOccurred()) expectedRole := t.NewRole() - checkMetadata(role, expectedRole) + t.checkMetadata(role, expectedRole) Expect(role.Rules).To(Equal(expectedRole.Rules)) binding := &rbacv1.RoleBinding{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, binding) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, binding) Expect(err).ToNot(HaveOccurred()) expectedBinding := t.NewRoleBinding() - checkMetadata(binding, expectedBinding) + t.checkMetadata(binding, expectedBinding) Expect(binding.Subjects).To(Equal(expectedBinding.Subjects)) Expect(binding.RoleRef).To(Equal(expectedBinding.RoleRef)) @@ -2093,9 +2091,9 @@ func (t *cryostatTestInput) expectRoutes() { func (t *cryostatTestInput) expectNoRoutes() { svc := &openshiftv1.Route{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, svc) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, svc) Expect(kerrors.IsNotFound(err)).To(BeTrue()) - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana", Namespace: t.Namespace}, svc) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana", Namespace: t.Namespace}, svc) Expect(kerrors.IsNotFound(err)).To(BeTrue()) } @@ -2104,13 +2102,13 @@ func (t *cryostatTestInput) checkIngresses() { expectedConfig := cr.Spec.NetworkOptions ingress := &netv1.Ingress{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, ingress) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, ingress) Expect(err).ToNot(HaveOccurred()) Expect(ingress.Annotations).To(Equal(expectedConfig.CoreConfig.Annotations)) Expect(ingress.Labels).To(Equal(expectedConfig.CoreConfig.Labels)) Expect(ingress.Spec).To(Equal(*expectedConfig.CoreConfig.IngressSpec)) - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana", Namespace: t.Namespace}, ingress) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana", Namespace: t.Namespace}, ingress) Expect(err).ToNot(HaveOccurred()) Expect(ingress.Annotations).To(Equal(expectedConfig.GrafanaConfig.Annotations)) Expect(ingress.Labels).To(Equal(expectedConfig.GrafanaConfig.Labels)) @@ -2124,15 +2122,15 @@ func (t *cryostatTestInput) expectIngresses() { func (t *cryostatTestInput) expectNoIngresses() { ing := &netv1.Ingress{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, ing) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, ing) Expect(kerrors.IsNotFound(err)).To(BeTrue()) - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana", Namespace: t.Namespace}, ing) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana", Namespace: t.Namespace}, ing) Expect(kerrors.IsNotFound(err)).To(BeTrue()) } func (t *cryostatTestInput) expectPVC(expectedPvc *corev1.PersistentVolumeClaim) { pvc := &corev1.PersistentVolumeClaim{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, pvc) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, pvc) Expect(kerrors.IsNotFound(err)).To(BeTrue()) t.reconcileCryostatFully() @@ -2142,11 +2140,11 @@ func (t *cryostatTestInput) expectPVC(expectedPvc *corev1.PersistentVolumeClaim) func (t *cryostatTestInput) checkPVC(expectedPVC *corev1.PersistentVolumeClaim) { pvc := &corev1.PersistentVolumeClaim{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, pvc) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, pvc) Expect(err).ToNot(HaveOccurred()) // Compare to desired spec - checkMetadata(pvc, expectedPVC) + t.checkMetadata(pvc, expectedPVC) Expect(pvc.Spec.AccessModes).To(Equal(expectedPVC.Spec.AccessModes)) Expect(pvc.Spec.StorageClassName).To(Equal(expectedPVC.Spec.StorageClassName)) Expect(pvc.Spec.VolumeName).To(Equal(expectedPVC.Spec.VolumeName)) @@ -2164,7 +2162,7 @@ func (t *cryostatTestInput) expectEmptyDir(expectedEmptyDir *corev1.EmptyDirVolu t.reconcileCryostatFully() deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) volume := deployment.Spec.Template.Spec.Volumes[0] @@ -2179,7 +2177,7 @@ func (t *cryostatTestInput) expectInMemoryDatabase() { t.reconcileCryostatFully() deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) containers := deployment.Spec.Template.Spec.Containers @@ -2189,7 +2187,7 @@ func (t *cryostatTestInput) expectInMemoryDatabase() { func (t *cryostatTestInput) expectGrafanaSecret() { secret := &corev1.Secret{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana-basic", Namespace: t.Namespace}, secret) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana-basic", Namespace: t.Namespace}, secret) Expect(kerrors.IsNotFound(err)).To(BeTrue()) t.reconcileCryostatFully() @@ -2198,18 +2196,18 @@ func (t *cryostatTestInput) expectGrafanaSecret() { func (t *cryostatTestInput) checkGrafanaSecret() { secret := &corev1.Secret{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-grafana-basic", Namespace: t.Namespace}, secret) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana-basic", Namespace: t.Namespace}, secret) Expect(err).ToNot(HaveOccurred()) // Compare to desired spec expectedSecret := t.NewGrafanaSecret() - checkMetadata(secret, expectedSecret) + t.checkMetadata(secret, expectedSecret) Expect(secret.StringData).To(Equal(expectedSecret.StringData)) } func (t *cryostatTestInput) expectCredentialsDatabaseSecret() { secret := &corev1.Secret{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-jmx-credentials-db", Namespace: t.Namespace}, secret) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-jmx-credentials-db", Namespace: t.Namespace}, secret) Expect(kerrors.IsNotFound(err)).To(BeTrue()) t.reconcileCryostatFully() @@ -2218,45 +2216,45 @@ func (t *cryostatTestInput) expectCredentialsDatabaseSecret() { func (t *cryostatTestInput) checkCredentialsDatabaseSecret() { secret := &corev1.Secret{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-jmx-credentials-db", Namespace: t.Namespace}, secret) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-jmx-credentials-db", Namespace: t.Namespace}, secret) Expect(err).ToNot(HaveOccurred()) // Compare to desired spec expectedSecret := t.NewCredentialsDatabaseSecret() - checkMetadata(secret, expectedSecret) + t.checkMetadata(secret, expectedSecret) Expect(secret.StringData).To(Equal(expectedSecret.StringData)) } func (t *cryostatTestInput) expectJMXSecret() { secret := &corev1.Secret{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-jmx-auth", Namespace: t.Namespace}, secret) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-jmx-auth", Namespace: t.Namespace}, secret) Expect(kerrors.IsNotFound(err)).To(BeTrue()) t.reconcileCryostatFully() - err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-jmx-auth", Namespace: t.Namespace}, secret) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-jmx-auth", Namespace: t.Namespace}, secret) Expect(err).ToNot(HaveOccurred()) expectedSecret := t.NewJMXSecret() - checkMetadata(secret, expectedSecret) + t.checkMetadata(secret, expectedSecret) Expect(secret.StringData).To(Equal(expectedSecret.StringData)) } func (t *cryostatTestInput) expectCoreService() { service := &corev1.Service{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, service) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, service) Expect(kerrors.IsNotFound(err)).To(BeTrue()) t.reconcileCryostatFully() - t.checkService("cryostat", t.NewCryostatService()) + t.checkService(t.Name, t.NewCryostatService()) } func (t *cryostatTestInput) expectStatusApplicationURL() { t.reconcileCryostatFully() instance := t.getCryostatInstance() - Expect(instance.Status.ApplicationURL).To(Equal("https://cryostat.example.com")) + Expect(instance.Status.ApplicationURL).To(Equal(fmt.Sprintf("https://%s.example.com", t.Name))) } func (t *cryostatTestInput) expectStatusGrafanaSecretName(secretName string) { @@ -2271,7 +2269,7 @@ func (t *cryostatTestInput) checkStatusGrafanaSecretName(secretName string) { func (t *cryostatTestInput) expectDeployment() { deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) Expect(kerrors.IsNotFound(err)).To(BeTrue()) t.reconcileCryostatFully() @@ -2281,7 +2279,7 @@ func (t *cryostatTestInput) expectDeployment() { func (t *cryostatTestInput) expectDeploymentHasCertSecrets() { t.reconcileCryostatFully() deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) volumes := deployment.Spec.Template.Spec.Volumes @@ -2312,7 +2310,7 @@ func (t *cryostatTestInput) expectCryostatFinalizerPresent() { } func (t *cryostatTestInput) checkGrafanaService() { - t.checkService("cryostat-grafana", t.NewGrafanaService()) + t.checkService(t.Name+"-grafana", t.NewGrafanaService()) } func (t *cryostatTestInput) checkService(svcName string, expected *corev1.Service) { @@ -2320,7 +2318,7 @@ func (t *cryostatTestInput) checkService(svcName string, expected *corev1.Servic err := t.Client.Get(context.Background(), types.NamespacedName{Name: svcName, Namespace: t.Namespace}, service) Expect(err).ToNot(HaveOccurred()) - checkMetadata(service, expected) + t.checkMetadata(service, expected) Expect(service.Spec.Type).To(Equal(expected.Spec.Type)) Expect(service.Spec.Selector).To(Equal(expected.Spec.Selector)) Expect(service.Spec.Ports).To(Equal(expected.Spec.Ports)) @@ -2334,7 +2332,7 @@ func (t *cryostatTestInput) expectNoService(svcName string) { func (t *cryostatTestInput) expectNoReportsDeployment() { deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-reports", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-reports", Namespace: t.Namespace}, deployment) Expect(kerrors.IsNotFound(err)).To(BeTrue()) } @@ -2398,18 +2396,18 @@ func (t *cryostatTestInput) setDeploymentConditions(deployName string, available func (t *cryostatTestInput) checkMainDeployment() { deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) cr := t.getCryostatInstance() - Expect(deployment.Name).To(Equal("cryostat")) + Expect(deployment.Name).To(Equal(t.Name)) Expect(deployment.Namespace).To(Equal(t.Namespace)) Expect(deployment.Annotations).To(Equal(map[string]string{ "app.openshift.io/connects-to": "cryostat-operator-controller-manager", })) Expect(deployment.Labels).To(Equal(map[string]string{ - "app": "cryostat", + "app": t.Name, "kind": "cryostat", "component": "cryostat", "app.kubernetes.io/name": "cryostat", @@ -2426,10 +2424,10 @@ func (t *cryostatTestInput) checkMainDeployment() { func (t *cryostatTestInput) checkMainPodTemplate(deployment *appsv1.Deployment, cr *model.CryostatInstance) { template := deployment.Spec.Template - Expect(template.Name).To(Equal("cryostat")) + Expect(template.Name).To(Equal(t.Name)) Expect(template.Namespace).To(Equal(t.Namespace)) Expect(template.Labels).To(Equal(map[string]string{ - "app": "cryostat", + "app": t.Name, "kind": "cryostat", "component": "cryostat", })) @@ -2438,18 +2436,18 @@ func (t *cryostatTestInput) checkMainPodTemplate(deployment *appsv1.Deployment, // Check that the networking environment variables are set correctly coreContainer := template.Spec.Containers[0] - port := "10000" + port := int32(10000) if cr.Spec.ServiceOptions != nil && cr.Spec.ServiceOptions.ReportsConfig != nil && cr.Spec.ServiceOptions.ReportsConfig.HTTPPort != nil { - port = strconv.Itoa(int(*cr.Spec.ServiceOptions.ReportsConfig.HTTPPort)) + port = *cr.Spec.ServiceOptions.ReportsConfig.HTTPPort } var reportsUrl string if t.ReportReplicas == 0 { reportsUrl = "" } else if t.TLS { - reportsUrl = "https://cryostat-reports:" + port + reportsUrl = fmt.Sprintf("https://%s-reports:%d", t.Name, port) } else { - reportsUrl = "http://cryostat-reports:" + port + reportsUrl = fmt.Sprintf("http://%s-reports:%d", t.Name, port) } ingress := !t.OpenShift && cr.Spec.NetworkOptions != nil && cr.Spec.NetworkOptions.CoreConfig != nil && cr.Spec.NetworkOptions.CoreConfig.IngressSpec != nil @@ -2472,7 +2470,7 @@ func (t *cryostatTestInput) checkMainPodTemplate(deployment *appsv1.Deployment, } // Check that the proper Service Account is set - Expect(template.Spec.ServiceAccountName).To(Equal("cryostat")) + Expect(template.Spec.ServiceAccountName).To(Equal(t.Name)) if cr.Spec.SchedulingOptions != nil { scheduling := cr.Spec.SchedulingOptions @@ -2488,18 +2486,18 @@ func (t *cryostatTestInput) checkMainPodTemplate(deployment *appsv1.Deployment, func (t *cryostatTestInput) checkReportsDeployment() { deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-reports", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-reports", Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) cr := t.getCryostatInstance() - Expect(deployment.Name).To(Equal("cryostat-reports")) + Expect(deployment.Name).To(Equal(t.Name + "-reports")) Expect(deployment.Namespace).To(Equal(t.Namespace)) Expect(deployment.Annotations).To(Equal(map[string]string{ - "app.openshift.io/connects-to": "cryostat", + "app.openshift.io/connects-to": t.Name, })) Expect(deployment.Labels).To(Equal(map[string]string{ - "app": "cryostat", + "app": t.Name, "kind": "cryostat", "component": "reports", "app.kubernetes.io/name": "cryostat-reports", @@ -2512,10 +2510,10 @@ func (t *cryostatTestInput) checkReportsDeployment() { // compare Pod template template := deployment.Spec.Template - Expect(template.Name).To(Equal("cryostat-reports")) + Expect(template.Name).To(Equal(t.Name + "-reports")) Expect(template.Namespace).To(Equal(t.Namespace)) Expect(template.Labels).To(Equal(map[string]string{ - "app": "cryostat", + "app": t.Name, "kind": "cryostat", "component": "reports", })) @@ -2542,7 +2540,7 @@ func (t *cryostatTestInput) checkReportsDeployment() { func (t *cryostatTestInput) checkDeploymentHasTemplates() { deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) volumes := deployment.Spec.Template.Spec.Volumes @@ -2556,7 +2554,7 @@ func (t *cryostatTestInput) checkDeploymentHasTemplates() { func (t *cryostatTestInput) checkDeploymentHasAuthProperties() { deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) volumes := deployment.Spec.Template.Spec.Volumes @@ -2573,7 +2571,7 @@ func (t *cryostatTestInput) checkDeploymentHasAuthProperties() { func (t *cryostatTestInput) checkDeploymentHasNoAuthProperties() { deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) volumes := deployment.Spec.Template.Spec.Volumes @@ -2594,7 +2592,7 @@ func (t *cryostatTestInput) checkCoreContainer(container *corev1.Container, ingr emptyDir bool, builtInDiscoveryDisabled bool, dbSecretProvided bool, resources *corev1.ResourceRequirements, securityContext *corev1.SecurityContext) { - Expect(container.Name).To(Equal("cryostat")) + Expect(container.Name).To(Equal(t.Name)) if t.EnvCoreImageTag == nil { Expect(container.Image).To(HavePrefix("quay.io/cryostat/cryostat:")) } else { @@ -2612,7 +2610,7 @@ func (t *cryostatTestInput) checkCoreContainer(container *corev1.Container, ingr } func (t *cryostatTestInput) checkGrafanaContainer(container *corev1.Container, resources *corev1.ResourceRequirements, securityContext *corev1.SecurityContext) { - Expect(container.Name).To(Equal("cryostat-grafana")) + Expect(container.Name).To(Equal(t.Name + "-grafana")) if t.EnvGrafanaImageTag == nil { Expect(container.Image).To(HavePrefix("quay.io/cryostat/cryostat-grafana-dashboard:")) } else { @@ -2629,7 +2627,7 @@ func (t *cryostatTestInput) checkGrafanaContainer(container *corev1.Container, r } func (t *cryostatTestInput) checkDatasourceContainer(container *corev1.Container, resources *corev1.ResourceRequirements, securityContext *corev1.SecurityContext) { - Expect(container.Name).To(Equal("cryostat-jfr-datasource")) + Expect(container.Name).To(Equal(t.Name + "-jfr-datasource")) if t.EnvDatasourceImageTag == nil { Expect(container.Image).To(HavePrefix("quay.io/cryostat/jfr-datasource:")) } else { @@ -2646,7 +2644,7 @@ func (t *cryostatTestInput) checkDatasourceContainer(container *corev1.Container } func (t *cryostatTestInput) checkReportsContainer(container *corev1.Container, resources *corev1.ResourceRequirements, securityContext *corev1.SecurityContext) { - Expect(container.Name).To(Equal("cryostat-reports")) + Expect(container.Name).To(Equal(t.Name + "-reports")) if t.EnvReportsImageTag == nil { Expect(container.Image).To(HavePrefix("quay.io/cryostat/cryostat-reports:")) } else { @@ -2663,7 +2661,7 @@ func (t *cryostatTestInput) checkReportsContainer(container *corev1.Container, r func (t *cryostatTestInput) checkCoreHasEnvironmentVariables(expectedEnvVars []corev1.EnvVar) { deployment := &appsv1.Deployment{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, deployment) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) Expect(err).ToNot(HaveOccurred()) template := deployment.Spec.Template @@ -2718,14 +2716,14 @@ func (t *cryostatTestInput) getCryostatInstance() *model.CryostatInstance { func (t *cryostatTestInput) lookupCryostatInstance() (*model.CryostatInstance, error) { if t.ClusterScoped { cr := &operatorv1beta1.ClusterCryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat"}, cr) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name}, cr) if err != nil { return nil, err } return t.ConvertClusterToModel(cr), nil } else { cr := &operatorv1beta1.Cryostat{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: t.Namespace}, cr) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, cr) if err != nil { return nil, err } diff --git a/internal/test/resources.go b/internal/test/resources.go index 8db1a7311..a1cdfcf9a 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -63,6 +63,7 @@ import ( ) type TestResources struct { + Name string Namespace string Minimal bool TLS bool @@ -114,7 +115,7 @@ func (r *TestResources) NewCryostat() *model.CryostatInstance { if r.ClusterScoped { cr := &operatorv1beta1.ClusterCryostat{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", // FIXME name conflicts + Name: r.Name, }, Spec: operatorv1beta1.ClusterCryostatSpec{ InstallNamespace: r.Namespace, @@ -129,7 +130,7 @@ func (r *TestResources) NewCryostat() *model.CryostatInstance { } else { cr := &operatorv1beta1.Cryostat{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, }, Spec: operatorv1beta1.CryostatSpec{ @@ -661,26 +662,26 @@ func (r *TestResources) NewCryostatService() *corev1.Service { c := true return &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, OwnerReferences: []metav1.OwnerReference{ { APIVersion: operatorv1beta1.GroupVersion.String(), Kind: "Cryostat", - Name: "cryostat", + Name: r.Name, UID: "", Controller: &c, }, }, Labels: map[string]string{ - "app": "cryostat", + "app": r.Name, "component": "cryostat", }, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeClusterIP, Selector: map[string]string{ - "app": "cryostat", + "app": r.Name, "component": "cryostat", }, Ports: []corev1.ServicePort{ @@ -703,26 +704,26 @@ func (r *TestResources) NewGrafanaService() *corev1.Service { c := true return &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-grafana", + Name: r.Name + "-grafana", Namespace: r.Namespace, OwnerReferences: []metav1.OwnerReference{ { APIVersion: operatorv1beta1.GroupVersion.String(), Kind: "Cryostat", - Name: "cryostat", + Name: r.Name, UID: "", Controller: &c, }, }, Labels: map[string]string{ - "app": "cryostat", + "app": r.Name, "component": "cryostat", }, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeClusterIP, Selector: map[string]string{ - "app": "cryostat", + "app": r.Name, "component": "cryostat", }, Ports: []corev1.ServicePort{ @@ -740,26 +741,26 @@ func (r *TestResources) NewReportsService() *corev1.Service { c := true return &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-reports", + Name: r.Name + "-reports", Namespace: r.Namespace, OwnerReferences: []metav1.OwnerReference{ { APIVersion: operatorv1beta1.GroupVersion.String(), Kind: "Cryostat", - Name: "cryostat-reports", + Name: r.Name + "-reports", UID: "", Controller: &c, }, }, Labels: map[string]string{ - "app": "cryostat", + "app": r.Name, "component": "reports", }, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeClusterIP, Selector: map[string]string{ - "app": "cryostat", + "app": r.Name, "component": "reports", }, Ports: []corev1.ServicePort{ @@ -782,7 +783,7 @@ func (r *TestResources) NewCustomizedCoreService() *corev1.Service { "my/custom": "annotation", } svc.Labels = map[string]string{ - "app": "cryostat", + "app": r.Name, "component": "cryostat", "my": "label", } @@ -797,7 +798,7 @@ func (r *TestResources) NewCustomizedGrafanaService() *corev1.Service { "my/custom": "annotation", } svc.Labels = map[string]string{ - "app": "cryostat", + "app": r.Name, "component": "cryostat", "my": "label", } @@ -812,7 +813,7 @@ func (r *TestResources) NewCustomizedReportsService() *corev1.Service { "my/custom": "annotation", } svc.Labels = map[string]string{ - "app": "cryostat", + "app": r.Name, "component": "reports", "my": "label", } @@ -840,7 +841,7 @@ func (r *TestResources) NewTestService() *corev1.Service { func (r *TestResources) NewGrafanaSecret() *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-grafana-basic", + Name: r.Name + "-grafana-basic", Namespace: r.Namespace, }, StringData: map[string]string{ @@ -853,7 +854,7 @@ func (r *TestResources) NewGrafanaSecret() *corev1.Secret { func (r *TestResources) OtherGrafanaSecret() *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-grafana-basic", + Name: r.Name + "-grafana-basic", Namespace: r.Namespace, }, StringData: map[string]string{ @@ -866,7 +867,7 @@ func (r *TestResources) OtherGrafanaSecret() *corev1.Secret { func (r *TestResources) NewCredentialsDatabaseSecret() *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-jmx-credentials-db", + Name: r.Name + "-jmx-credentials-db", Namespace: r.Namespace, }, StringData: map[string]string{ @@ -878,7 +879,7 @@ func (r *TestResources) NewCredentialsDatabaseSecret() *corev1.Secret { func (r *TestResources) OtherCredentialsDatabaseSecret() *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-jmx-credentials-db", + Name: r.Name + "-jmx-credentials-db", Namespace: r.Namespace, }, StringData: map[string]string{ @@ -890,7 +891,7 @@ func (r *TestResources) OtherCredentialsDatabaseSecret() *corev1.Secret { func (r *TestResources) NewJMXSecret() *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-jmx-auth", + Name: r.Name + "-jmx-auth", Namespace: r.Namespace, }, StringData: map[string]string{ @@ -903,7 +904,7 @@ func (r *TestResources) NewJMXSecret() *corev1.Secret { func (r *TestResources) NewKeystoreSecret() *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-keystore", + Name: r.Name + "-keystore", Namespace: r.Namespace, }, StringData: map[string]string{ @@ -915,7 +916,7 @@ func (r *TestResources) NewKeystoreSecret() *corev1.Secret { func (r *TestResources) OtherJMXSecret() *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-jmx-auth", + Name: r.Name + "-jmx-auth", Namespace: r.Namespace, }, StringData: map[string]string{ @@ -940,30 +941,30 @@ func (r *TestResources) NewTestCertSecret(name string) *corev1.Secret { func (r *TestResources) NewCryostatCert() *certv1.Certificate { return &certv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, }, Spec: certv1.CertificateSpec{ - CommonName: fmt.Sprintf("cryostat.%s.svc", r.Namespace), + CommonName: fmt.Sprintf(r.Name+".%s.svc", r.Namespace), DNSNames: []string{ - "cryostat", - fmt.Sprintf("cryostat.%s.svc", r.Namespace), - fmt.Sprintf("cryostat.%s.svc.cluster.local", r.Namespace), + r.Name, + fmt.Sprintf(r.Name+".%s.svc", r.Namespace), + fmt.Sprintf(r.Name+".%s.svc.cluster.local", r.Namespace), }, - SecretName: "cryostat-tls", + SecretName: r.Name + "-tls", Keystores: &certv1.CertificateKeystores{ PKCS12: &certv1.PKCS12Keystore{ Create: true, PasswordSecretRef: certMeta.SecretKeySelector{ LocalObjectReference: certMeta.LocalObjectReference{ - Name: "cryostat-keystore", + Name: r.Name + "-keystore", }, Key: "KEYSTORE_PASS", }, }, }, IssuerRef: certMeta.ObjectReference{ - Name: "cryostat-ca", + Name: r.Name + "-ca", }, Usages: []certv1.KeyUsage{ certv1.UsageDigitalSignature, @@ -978,20 +979,20 @@ func (r *TestResources) NewCryostatCert() *certv1.Certificate { func (r *TestResources) NewGrafanaCert() *certv1.Certificate { return &certv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-grafana", + Name: r.Name + "-grafana", Namespace: r.Namespace, }, Spec: certv1.CertificateSpec{ - CommonName: fmt.Sprintf("cryostat-grafana.%s.svc", r.Namespace), + CommonName: fmt.Sprintf(r.Name+"-grafana.%s.svc", r.Namespace), DNSNames: []string{ - "cryostat-grafana", - fmt.Sprintf("cryostat-grafana.%s.svc", r.Namespace), - fmt.Sprintf("cryostat-grafana.%s.svc.cluster.local", r.Namespace), + r.Name + "-grafana", + fmt.Sprintf(r.Name+"-grafana.%s.svc", r.Namespace), + fmt.Sprintf(r.Name+"-grafana.%s.svc.cluster.local", r.Namespace), "cryostat-health.local", }, - SecretName: "cryostat-grafana-tls", + SecretName: r.Name + "-grafana-tls", IssuerRef: certMeta.ObjectReference{ - Name: "cryostat-ca", + Name: r.Name + "-ca", }, Usages: []certv1.KeyUsage{ certv1.UsageDigitalSignature, @@ -1005,19 +1006,19 @@ func (r *TestResources) NewGrafanaCert() *certv1.Certificate { func (r *TestResources) NewReportsCert() *certv1.Certificate { return &certv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-reports", + Name: r.Name + "-reports", Namespace: r.Namespace, }, Spec: certv1.CertificateSpec{ - CommonName: fmt.Sprintf("cryostat-reports.%s.svc", r.Namespace), + CommonName: fmt.Sprintf(r.Name+"-reports.%s.svc", r.Namespace), DNSNames: []string{ - "cryostat-reports", - fmt.Sprintf("cryostat-reports.%s.svc", r.Namespace), - fmt.Sprintf("cryostat-reports.%s.svc.cluster.local", r.Namespace), + r.Name + "-reports", + fmt.Sprintf(r.Name+"-reports.%s.svc", r.Namespace), + fmt.Sprintf(r.Name+"-reports.%s.svc.cluster.local", r.Namespace), }, - SecretName: "cryostat-reports-tls", + SecretName: r.Name + "-reports-tls", IssuerRef: certMeta.ObjectReference{ - Name: "cryostat-ca", + Name: r.Name + "-ca", }, Usages: []certv1.KeyUsage{ certv1.UsageDigitalSignature, @@ -1031,14 +1032,14 @@ func (r *TestResources) NewReportsCert() *certv1.Certificate { func (r *TestResources) NewCACert() *certv1.Certificate { return &certv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-ca", + Name: r.Name + "-ca", Namespace: r.Namespace, }, Spec: certv1.CertificateSpec{ - CommonName: "ca.cryostat.cert-manager", - SecretName: "cryostat-ca", + CommonName: fmt.Sprintf("ca.%s.cert-manager", r.Name), + SecretName: r.Name + "-ca", IssuerRef: certMeta.ObjectReference{ - Name: "cryostat-self-signed", + Name: r.Name + "-self-signed", }, IsCA: true, }, @@ -1048,7 +1049,7 @@ func (r *TestResources) NewCACert() *certv1.Certificate { func (r *TestResources) NewSelfSignedIssuer() *certv1.Issuer { return &certv1.Issuer{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-self-signed", + Name: r.Name + "-self-signed", Namespace: r.Namespace, }, Spec: certv1.IssuerSpec{ @@ -1062,13 +1063,13 @@ func (r *TestResources) NewSelfSignedIssuer() *certv1.Issuer { func (r *TestResources) NewCryostatCAIssuer() *certv1.Issuer { return &certv1.Issuer{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-ca", + Name: r.Name + "-ca", Namespace: r.Namespace, }, Spec: certv1.IssuerSpec{ IssuerConfig: certv1.IssuerConfig{ CA: &certv1.CAIssuer{ - SecretName: "cryostat-ca", + SecretName: r.Name + "-ca", }, }, }, @@ -1079,7 +1080,7 @@ func (r *TestResources) newPVC(spec *corev1.PersistentVolumeClaimSpec, labels ma annotations map[string]string) *corev1.PersistentVolumeClaim { return &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, Annotations: annotations, Labels: labels, @@ -1097,7 +1098,7 @@ func (r *TestResources) NewDefaultPVC() *corev1.PersistentVolumeClaim { }, }, }, map[string]string{ - "app": "cryostat", + "app": r.Name, }, nil) } @@ -1113,7 +1114,7 @@ func (r *TestResources) NewCustomPVC() *corev1.PersistentVolumeClaim { }, }, map[string]string{ "my": "label", - "app": "cryostat", + "app": r.Name, }, map[string]string{ "my/custom": "annotation", }) @@ -1130,7 +1131,7 @@ func (r *TestResources) NewCustomPVCSomeDefault() *corev1.PersistentVolumeClaim }, }, }, map[string]string{ - "app": "cryostat", + "app": r.Name, }, nil) } @@ -1143,7 +1144,7 @@ func (r *TestResources) NewDefaultPVCWithLabel() *corev1.PersistentVolumeClaim { }, }, }, map[string]string{ - "app": "cryostat", + "app": r.Name, "my": "label", }, nil) } @@ -1291,7 +1292,7 @@ func (r *TestResources) NewCoreEnvironmentVariables(reportsUrl string, authProps } else { envs = append(envs, corev1.EnvVar{ Name: "KEYSTORE_PATH", - Value: "/var/run/secrets/operator.cryostat.io/cryostat-tls/keystore.p12", + Value: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s-tls/keystore.p12", r.Name), }) } @@ -1307,7 +1308,7 @@ func (r *TestResources) NewCoreEnvironmentVariables(reportsUrl string, authProps }, corev1.EnvVar{ Name: "CRYOSTAT_OAUTH_CLIENT_ID", - Value: "cryostat", + Value: r.Name, }, corev1.EnvVar{ Name: "CRYOSTAT_BASE_OAUTH_ROLE", @@ -1361,11 +1362,11 @@ func (r *TestResources) DatabaseConfigEnvironmentVariables() []corev1.EnvVar { }, { Name: "CRYOSTAT_JDBC_USERNAME", - Value: "cryostat", + Value: r.Name, }, { Name: "CRYOSTAT_JDBC_PASSWORD", - Value: "cryostat", + Value: r.Name, }, } } @@ -1374,7 +1375,7 @@ func (r *TestResources) newNetworkEnvironmentVariables() []corev1.EnvVar { envs := []corev1.EnvVar{ { Name: "CRYOSTAT_WEB_HOST", - Value: "cryostat.example.com", + Value: r.Name + ".example.com", }, } if r.ExternalTLS { @@ -1395,13 +1396,13 @@ func (r *TestResources) newNetworkEnvironmentVariables() []corev1.EnvVar { envs = append(envs, corev1.EnvVar{ Name: "GRAFANA_DASHBOARD_EXT_URL", - Value: "https://cryostat-grafana.example.com", + Value: fmt.Sprintf("https://%s-grafana.example.com", r.Name), }) } else { envs = append(envs, corev1.EnvVar{ Name: "GRAFANA_DASHBOARD_EXT_URL", - Value: "http://cryostat-grafana.example.com", + Value: fmt.Sprintf("http://%s-grafana.example.com", r.Name), }) } if r.TLS { @@ -1434,10 +1435,10 @@ func (r *TestResources) NewGrafanaEnvironmentVariables() []corev1.EnvVar { Value: "https", }, corev1.EnvVar{ Name: "GF_SERVER_CERT_KEY", - Value: "/var/run/secrets/operator.cryostat.io/cryostat-grafana-tls/tls.key", + Value: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s-grafana-tls/tls.key", r.Name), }, corev1.EnvVar{ Name: "GF_SERVER_CERT_FILE", - Value: "/var/run/secrets/operator.cryostat.io/cryostat-grafana-tls/tls.crt", + Value: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s-grafana-tls/tls.crt", r.Name), }) } return envs @@ -1476,10 +1477,10 @@ func (r *TestResources) NewReportsEnvironmentVariables(resources *corev1.Resourc Value: "10000", }, corev1.EnvVar{ Name: "QUARKUS_HTTP_SSL_CERTIFICATE_KEY_FILE", - Value: "/var/run/secrets/operator.cryostat.io/cryostat-reports-tls/tls.key", + Value: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s-reports-tls/tls.key", r.Name), }, corev1.EnvVar{ Name: "QUARKUS_HTTP_SSL_CERTIFICATE_FILE", - Value: "/var/run/secrets/operator.cryostat.io/cryostat-reports-tls/tls.crt", + Value: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s-reports-tls/tls.crt", r.Name), }, corev1.EnvVar{ Name: "QUARKUS_HTTP_INSECURE_REQUESTS", Value: "disabled", @@ -1498,7 +1499,7 @@ func (r *TestResources) NewCoreEnvFromSource() []corev1.EnvFromSource { { SecretRef: &corev1.SecretEnvSource{ LocalObjectReference: corev1.LocalObjectReference{ - Name: "cryostat-jmx-auth", + Name: r.Name + "-jmx-auth", }, }, }, @@ -1507,7 +1508,7 @@ func (r *TestResources) NewCoreEnvFromSource() []corev1.EnvFromSource { envsFrom = append(envsFrom, corev1.EnvFromSource{ SecretRef: &corev1.SecretEnvSource{ LocalObjectReference: corev1.LocalObjectReference{ - Name: "cryostat-keystore", + Name: r.Name + "-keystore", }, }, }) @@ -1520,7 +1521,7 @@ func (r *TestResources) NewGrafanaEnvFromSource() []corev1.EnvFromSource { { SecretRef: &corev1.SecretEnvSource{ LocalObjectReference: corev1.LocalObjectReference{ - Name: "cryostat-grafana-basic", + Name: r.Name + "-grafana-basic", }, }, }, @@ -1561,37 +1562,37 @@ func (r *TestResources) NewJmxCacheOptionsEnv() []corev1.EnvVar { func (r *TestResources) NewCoreVolumeMounts() []corev1.VolumeMount { mounts := []corev1.VolumeMount{ { - Name: "cryostat", + Name: r.Name, ReadOnly: false, MountPath: "/opt/cryostat.d/conf.d", SubPath: "config", }, { - Name: "cryostat", + Name: r.Name, ReadOnly: false, MountPath: "/opt/cryostat.d/recordings.d", SubPath: "flightrecordings", }, { - Name: "cryostat", + Name: r.Name, ReadOnly: false, MountPath: "/opt/cryostat.d/templates.d", SubPath: "templates", }, { - Name: "cryostat", + Name: r.Name, ReadOnly: false, MountPath: "/opt/cryostat.d/clientlib.d", SubPath: "clientlib", }, { - Name: "cryostat", + Name: r.Name, ReadOnly: false, MountPath: "/opt/cryostat.d/probes.d", SubPath: "probes", }, { - Name: "cryostat", + Name: r.Name, ReadOnly: false, MountPath: "truststore", SubPath: "truststore", @@ -1607,7 +1608,7 @@ func (r *TestResources) NewCoreVolumeMounts() []corev1.VolumeMount { corev1.VolumeMount{ Name: "keystore", ReadOnly: true, - MountPath: "/var/run/secrets/operator.cryostat.io/cryostat-tls", + MountPath: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s-tls", r.Name), }) } return mounts @@ -1619,7 +1620,7 @@ func (r *TestResources) NewGrafanaVolumeMounts() []corev1.VolumeMount { mounts = append(mounts, corev1.VolumeMount{ Name: "grafana-tls-secret", - MountPath: "/var/run/secrets/operator.cryostat.io/cryostat-grafana-tls", + MountPath: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s-grafana-tls", r.Name), ReadOnly: true, }) } @@ -1632,7 +1633,7 @@ func (r *TestResources) NewReportsVolumeMounts() []corev1.VolumeMount { mounts = append(mounts, corev1.VolumeMount{ Name: "reports-tls-secret", - MountPath: "/var/run/secrets/operator.cryostat.io/cryostat-reports-tls", + MountPath: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s-reports-tls", r.Name), ReadOnly: true, }) } @@ -1740,7 +1741,7 @@ func (r *TestResources) NewReportsLivenessProbe() *corev1.Probe { func (r *TestResources) NewMainDeploymentSelector() *metav1.LabelSelector { return &metav1.LabelSelector{ MatchLabels: map[string]string{ - "app": "cryostat", + "app": r.Name, "kind": "cryostat", "component": "cryostat", }, @@ -1750,7 +1751,7 @@ func (r *TestResources) NewMainDeploymentSelector() *metav1.LabelSelector { func (r *TestResources) NewReportsDeploymentSelector() *metav1.LabelSelector { return &metav1.LabelSelector{ MatchLabels: map[string]string{ - "app": "cryostat", + "app": r.Name, "kind": "cryostat", "component": "reports", }, @@ -1767,7 +1768,7 @@ func (r *TestResources) OtherDeployment() *appsv1.Deployment { replicas := int32(2) return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, Labels: map[string]string{ "app": "something-else", @@ -1781,7 +1782,7 @@ func (r *TestResources) OtherDeployment() *appsv1.Deployment { Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, Labels: map[string]string{ "app": "something-app", @@ -1908,10 +1909,10 @@ func (r *TestResources) newVolumes(certProjections []corev1.VolumeProjection) [] readOnlymode := int32(0440) volumes := []corev1.Volume{ { - Name: "cryostat", + Name: r.Name, VolumeSource: corev1.VolumeSource{ PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "cryostat", + ClaimName: r.Name, ReadOnly: false, }, }, @@ -1922,12 +1923,12 @@ func (r *TestResources) newVolumes(certProjections []corev1.VolumeProjection) [] projs = append(projs, corev1.VolumeProjection{ Secret: &corev1.SecretProjection{ LocalObjectReference: corev1.LocalObjectReference{ - Name: "cryostat-tls", + Name: r.Name + "-tls", }, Items: []corev1.KeyToPath{ { Key: "ca.crt", - Path: "cryostat-ca.crt", + Path: r.Name + "-ca.crt", Mode: &readOnlymode, }, }, @@ -1939,7 +1940,7 @@ func (r *TestResources) newVolumes(certProjections []corev1.VolumeProjection) [] Name: "keystore", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: "cryostat-tls", + SecretName: r.Name + "-tls", Items: []corev1.KeyToPath{ { Key: "keystore.p12", @@ -1956,7 +1957,7 @@ func (r *TestResources) newVolumes(certProjections []corev1.VolumeProjection) [] Name: "grafana-tls-secret", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: "cryostat-grafana-tls", + SecretName: r.Name + "-grafana-tls", }, }, }) @@ -1985,7 +1986,7 @@ func (r *TestResources) NewReportsVolumes() []corev1.Volume { Name: "reports-tls-secret", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: "cryostat-reports-tls", + SecretName: r.Name + "-reports-tls", }, }, }, @@ -2063,7 +2064,7 @@ func (r *TestResources) NewReportSecurityContext(cr *model.CryostatInstance) *co } func (r *TestResources) NewCoreRoute() *routev1.Route { - return r.newRoute("cryostat", 8181) + return r.newRoute(r.Name, 8181) } func (r *TestResources) NewCustomCoreRoute() *routev1.Route { @@ -2074,7 +2075,7 @@ func (r *TestResources) NewCustomCoreRoute() *routev1.Route { } func (r *TestResources) NewGrafanaRoute() *routev1.Route { - return r.newRoute("cryostat-grafana", 3000) + return r.newRoute(r.Name+"-grafana", 3000) } func (r *TestResources) NewCustomGrafanaRoute() *routev1.Route { @@ -2094,7 +2095,7 @@ func (r *TestResources) newRoute(name string, port int) *routev1.Route { } else { routeTLS = &routev1.TLSConfig{ Termination: routev1.TLSTerminationReencrypt, - DestinationCACertificate: "cryostat-ca-bytes", + DestinationCACertificate: r.Name + "-ca-bytes", } } return &routev1.Route{ @@ -2118,7 +2119,7 @@ func (r *TestResources) newRoute(name string, port int) *routev1.Route { func (r *TestResources) OtherCoreRoute() *routev1.Route { return &routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, Annotations: map[string]string{"custom": "annotation"}, Labels: map[string]string{"custom": "label"}, @@ -2144,7 +2145,7 @@ func (r *TestResources) OtherCoreRoute() *routev1.Route { func (r *TestResources) OtherGrafanaRoute() *routev1.Route { return &routev1.Route{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-grafana", + Name: r.Name + "-grafana", Namespace: r.Namespace, Annotations: map[string]string{"grafana": "annotation"}, Labels: map[string]string{"grafana": "label"}, @@ -2165,7 +2166,7 @@ func (r *TestResources) OtherCoreIngress() *netv1.Ingress { pathtype := netv1.PathTypePrefix return &netv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, Annotations: map[string]string{"custom": "annotation"}, Labels: map[string]string{"custom": "label"}, @@ -2202,7 +2203,7 @@ func (r *TestResources) OtherGrafanaIngress() *netv1.Ingress { pathtype := netv1.PathTypePrefix return &netv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-grafana", + Name: r.Name + "-grafana", Namespace: r.Namespace, Annotations: map[string]string{"grafana": "annotation"}, Labels: map[string]string{"grafana": "label"}, @@ -2292,16 +2293,16 @@ func (r *TestResources) NewServiceAccount() *corev1.ServiceAccount { var annotations map[string]string if r.OpenShift { annotations = map[string]string{ - "serviceaccounts.openshift.io/oauth-redirectreference.route": `{"metadata":{"creationTimestamp":null},"reference":{"group":"","kind":"Route","name":"cryostat"}}`, + "serviceaccounts.openshift.io/oauth-redirectreference.route": fmt.Sprintf(`{"metadata":{"creationTimestamp":null},"reference":{"group":"","kind":"Route","name":"%s"}}`, r.Name), } } return &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, Labels: map[string]string{ - "app": "cryostat", + "app": r.Name, }, Annotations: annotations, }, @@ -2312,7 +2313,7 @@ func (r *TestResources) OtherServiceAccount() *corev1.ServiceAccount { disable := false return &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, Labels: map[string]string{ "app": "not-cryostat", @@ -2324,15 +2325,15 @@ func (r *TestResources) OtherServiceAccount() *corev1.ServiceAccount { }, ImagePullSecrets: []corev1.LocalObjectReference{ { - Name: "cryostat-dockercfg-abcde", + Name: r.Name + "-dockercfg-abcde", }, }, Secrets: []corev1.ObjectReference{ { - Name: "cryostat-dockercfg-abcde", + Name: r.Name + "-dockercfg-abcde", }, { - Name: "cryostat-token-abcde", + Name: r.Name + "-token-abcde", }, }, AutomountServiceAccountToken: &disable, @@ -2369,7 +2370,7 @@ func (r *TestResources) NewRole() *rbacv1.Role { } return &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, }, Rules: rules, @@ -2379,7 +2380,7 @@ func (r *TestResources) NewRole() *rbacv1.Role { func (r *TestResources) OtherRole() *rbacv1.Role { return &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, Labels: map[string]string{ "test": "label", @@ -2418,20 +2419,20 @@ func (r *TestResources) NewAuthClusterRole() *rbacv1.ClusterRole { func (r *TestResources) NewRoleBinding() *rbacv1.RoleBinding { return &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, }, Subjects: []rbacv1.Subject{ { Kind: rbacv1.ServiceAccountKind, - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, }, }, RoleRef: rbacv1.RoleRef{ APIGroup: "rbac.authorization.k8s.io", Kind: "Role", - Name: "cryostat", + Name: r.Name, }, } } @@ -2439,7 +2440,7 @@ func (r *TestResources) NewRoleBinding() *rbacv1.RoleBinding { func (r *TestResources) OtherRoleBinding() *rbacv1.RoleBinding { return &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, Labels: map[string]string{ "test": "label", @@ -2465,7 +2466,7 @@ func (r *TestResources) OtherRoleBinding() *rbacv1.RoleBinding { } func (r *TestResources) clusterUniqueSuffix() string { - toEncode := r.Namespace + "/cryostat" + toEncode := r.Namespace + "/" + r.Name return fmt.Sprintf("%x", sha256.Sum256([]byte(toEncode))) } @@ -2477,7 +2478,7 @@ func (r *TestResources) NewClusterRoleBinding() *rbacv1.ClusterRoleBinding { Subjects: []rbacv1.Subject{ { Kind: rbacv1.ServiceAccountKind, - Name: "cryostat", + Name: r.Name, Namespace: r.Namespace, }, }, @@ -2576,7 +2577,7 @@ func (r *TestResources) NewConsoleLink() *consolev1.ConsoleLink { Spec: consolev1.ConsoleLinkSpec{ Link: consolev1.Link{ Text: "Cryostat", - Href: "https://cryostat.example.com", + Href: fmt.Sprintf("https://%s.example.com", r.Name), }, Location: consolev1.NamespaceDashboard, NamespaceDashboard: &consolev1.NamespaceDashboardSpec{ From 267ec4fb4c0c63d2d723afba51590b35bee9cde7 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Mon, 23 Jan 2023 18:45:05 -0500 Subject: [PATCH 04/21] Refactor tests to share between APIs --- internal/controllers/reconciler_test.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index d0a4765db..7713db0a4 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -79,10 +79,7 @@ type cryostatTestInput struct { *test.TestResources } -// TODO Can we move the specs to a separate common file and execute them with each type of CRD? -var _ = Describe("CryostatController", func() { - var t *cryostatTestInput - +var CommonJustBeforeEach = func(t *cryostatTestInput) { JustBeforeEach(func() { logger := zap.New() logf.SetLogger(logger) @@ -103,9 +100,11 @@ var _ = Describe("CryostatController", func() { ReconcilerTLS: test.NewTestReconcilerTLS(&t.TestReconcilerConfig), }) }) +} +var CommonBeforeEach = func(t *cryostatTestInput) { BeforeEach(func() { - t = &cryostatTestInput{ + input := cryostatTestInput{ TestReconcilerConfig: test.TestReconcilerConfig{ GeneratedPasswords: []string{"grafana", "credentials_database", "jmx", "keystore"}, }, @@ -117,16 +116,15 @@ var _ = Describe("CryostatController", func() { OpenShift: true, }, } - t.objs = []runtime.Object{ - t.NewNamespace(), - t.NewApiServer(), + input.objs = []runtime.Object{ + input.NewNamespace(), + input.NewApiServer(), } + *t = input }) +} - AfterEach(func() { - t = nil - }) - +var CommonSpecs = func(t *cryostatTestInput) { Describe("reconciling a request in OpenShift", func() { expectSuccessful := func() { It("should create certificates", func() { @@ -1918,7 +1916,7 @@ var _ = Describe("CryostatController", func() { }) }) }) -}) +} func (t *cryostatTestInput) checkRoutes() { if !t.Minimal { From db17ecd0f31d462891852ab3717884b3be0d89ba Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Wed, 25 Jan 2023 13:55:16 -0500 Subject: [PATCH 05/21] Run tests for ClusterCryostat controller --- .../cluster_cryostat_controller.go | 7 + .../cluster_cryostat_controller_test.go | 55 ++++++++ internal/controllers/cryostat_controller.go | 130 ++++++++++++++++++ .../controllers/cryostat_controller_test.go | 55 ++++++++ internal/controllers/reconciler.go | 5 + internal/controllers/reconciler_test.go | 94 ++++++++----- internal/test/resources.go | 6 + 7 files changed, 314 insertions(+), 38 deletions(-) create mode 100644 internal/controllers/cluster_cryostat_controller_test.go create mode 100644 internal/controllers/cryostat_controller.go create mode 100644 internal/controllers/cryostat_controller_test.go diff --git a/internal/controllers/cluster_cryostat_controller.go b/internal/controllers/cluster_cryostat_controller.go index 54104c337..1ff43dc11 100644 --- a/internal/controllers/cluster_cryostat_controller.go +++ b/internal/controllers/cluster_cryostat_controller.go @@ -58,6 +58,9 @@ import ( // Generates constants from environment variables at build time //go:generate go run ../tools/const_generator.go +// Verify that *ClusterCryostatReconciler implements ReconcilerInterface. +var _ ReconcilerInterface = (*ClusterCryostatReconciler)(nil) + // CryostatReconciler reconciles a Cryostat object type ClusterCryostatReconciler struct { delegate *Reconciler @@ -140,3 +143,7 @@ func (r *ClusterCryostatReconciler) SetupWithManager(mgr ctrl.Manager) error { return c.Complete(r) } + +func (r *ClusterCryostatReconciler) GetConfig() *ReconcilerConfig { + return r.ReconcilerConfig +} diff --git a/internal/controllers/cluster_cryostat_controller_test.go b/internal/controllers/cluster_cryostat_controller_test.go new file mode 100644 index 000000000..607cde006 --- /dev/null +++ b/internal/controllers/cluster_cryostat_controller_test.go @@ -0,0 +1,55 @@ +// Copyright The Cryostat Authors +// +// The Universal Permissive License (UPL), Version 1.0 +// +// Subject to the condition set forth below, permission is hereby granted to any +// person obtaining a copy of this software, associated documentation and/or data +// (collectively the "Software"), free of charge and under any and all copyright +// rights in the Software, and any and all patent rights owned or freely +// licensable by each licensor hereunder covering either (i) the unmodified +// Software as contributed to or provided by such licensor, or (ii) the Larger +// Works (as defined below), to deal in both +// +// (a) the Software, and +// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +// one is included with the Software (each a "Larger Work" to which the Software +// is contributed by such licensors), +// +// without restriction, including without limitation the rights to copy, create +// derivative works of, display, perform, and distribute the Software and make, +// use, sell, offer for sale, import, export, have made, and have sold the +// Software and the Larger Work(s), and to sublicense the foregoing rights on +// either these or other terms. +// +// This license is subject to the following condition: +// The above copyright notice and either this complete permission notice or at +// a minimum a reference to the UPL must be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package controllers_test + +import ( + "github.com/cryostatio/cryostat-operator/internal/controllers" + . "github.com/onsi/ginkgo" +) + +var _ = Describe("ClusterCryostatController", func() { + c := controllerTest{ + clusterScoped: true, + constructorFunc: newClusterCryostatController, + } + + c.commonTests() +}) + +func newClusterCryostatController(config *controllers.ReconcilerConfig) controllers.ReconcilerInterface { + return controllers.NewClusterCryostatReconciler(config) +} diff --git a/internal/controllers/cryostat_controller.go b/internal/controllers/cryostat_controller.go new file mode 100644 index 000000000..c09080841 --- /dev/null +++ b/internal/controllers/cryostat_controller.go @@ -0,0 +1,130 @@ +// Copyright The Cryostat Authors +// +// The Universal Permissive License (UPL), Version 1.0 +// +// Subject to the condition set forth below, permission is hereby granted to any +// person obtaining a copy of this software, associated documentation and/or data +// (collectively the "Software"), free of charge and under any and all copyright +// rights in the Software, and any and all patent rights owned or freely +// licensable by each licensor hereunder covering either (i) the unmodified +// Software as contributed to or provided by such licensor, or (ii) the Larger +// Works (as defined below), to deal in both +// +// (a) the Software, and +// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +// one is included with the Software (each a "Larger Work" to which the Software +// is contributed by such licensors), +// +// without restriction, including without limitation the rights to copy, create +// derivative works of, display, perform, and distribute the Software and make, +// use, sell, offer for sale, import, export, have made, and have sold the +// Software and the Larger Work(s), and to sublicense the foregoing rights on +// either these or other terms. +// +// This license is subject to the following condition: +// The above copyright notice and either this complete permission notice or at +// a minimum a reference to the UPL must be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package controllers + +import ( + "context" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + "github.com/cryostatio/cryostat-operator/internal/controllers/model" + + certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" + openshiftv1 "github.com/openshift/api/route/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + netv1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// Verify that *ClusterCryostatReconciler implements ReconcilerInterface. +var _ ReconcilerInterface = (*CryostatReconciler)(nil) + +// CryostatReconciler reconciles a Cryostat object +type CryostatReconciler struct { + delegate *Reconciler + *ReconcilerConfig +} + +func NewCryostatReconciler(config *ReconcilerConfig) *CryostatReconciler { + return &CryostatReconciler{ + ReconcilerConfig: config, + delegate: &Reconciler{ + ReconcilerConfig: config, + }, + } +} + +// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=cryostats,verbs=* +// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=cryostats/status,verbs=get;update;patch +// +kubebuilder:rbac:namespace=system,groups=operator.cryostat.io,resources=cryostats/finalizers,verbs=update + +// Reconcile processes a Cryostat CR and manages a Cryostat installation accordingly +func (r *CryostatReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { + reqLogger := r.Log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) + + reqLogger.Info("Reconciling Cryostat") + + // Fetch the Cryostat instance + cr := &operatorv1beta1.Cryostat{} + err := r.Client.Get(ctx, request.NamespacedName, cr) + if err != nil { + if kerrors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + reqLogger.Info("Cryostat instance not found") + return reconcile.Result{}, nil + } + reqLogger.Error(err, "Error reading Cryostat instance") + return reconcile.Result{}, err + } + + instance := model.FromCryostat(cr) + return r.delegate.ReconcileCryostat(ctx, instance) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *CryostatReconciler) SetupWithManager(mgr ctrl.Manager) error { + c := ctrl.NewControllerManagedBy(mgr). + For(&operatorv1beta1.Cryostat{}) + + // Watch for changes to secondary resources and requeue the owner Cryostat + resources := []client.Object{&appsv1.Deployment{}, &corev1.Service{}, &corev1.Secret{}, &corev1.PersistentVolumeClaim{}, + &corev1.ServiceAccount{}, &rbacv1.Role{}, &rbacv1.RoleBinding{}, &netv1.Ingress{}} + if r.IsOpenShift { + resources = append(resources, &openshiftv1.Route{}) + } + // Can only check this at startup + if r.IsCertManagerInstalled { + resources = append(resources, &certv1.Issuer{}, &certv1.Certificate{}) + } + + for _, resource := range resources { + c = c.Owns(resource) + } + + return c.Complete(r) +} + +func (r *CryostatReconciler) GetConfig() *ReconcilerConfig { + return r.ReconcilerConfig +} diff --git a/internal/controllers/cryostat_controller_test.go b/internal/controllers/cryostat_controller_test.go new file mode 100644 index 000000000..3741db152 --- /dev/null +++ b/internal/controllers/cryostat_controller_test.go @@ -0,0 +1,55 @@ +// Copyright The Cryostat Authors +// +// The Universal Permissive License (UPL), Version 1.0 +// +// Subject to the condition set forth below, permission is hereby granted to any +// person obtaining a copy of this software, associated documentation and/or data +// (collectively the "Software"), free of charge and under any and all copyright +// rights in the Software, and any and all patent rights owned or freely +// licensable by each licensor hereunder covering either (i) the unmodified +// Software as contributed to or provided by such licensor, or (ii) the Larger +// Works (as defined below), to deal in both +// +// (a) the Software, and +// (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +// one is included with the Software (each a "Larger Work" to which the Software +// is contributed by such licensors), +// +// without restriction, including without limitation the rights to copy, create +// derivative works of, display, perform, and distribute the Software and make, +// use, sell, offer for sale, import, export, have made, and have sold the +// Software and the Larger Work(s), and to sublicense the foregoing rights on +// either these or other terms. +// +// This license is subject to the following condition: +// The above copyright notice and either this complete permission notice or at +// a minimum a reference to the UPL must be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package controllers_test + +import ( + "github.com/cryostatio/cryostat-operator/internal/controllers" + . "github.com/onsi/ginkgo" +) + +var _ = Describe("CryostatController", func() { + c := &controllerTest{ + clusterScoped: false, + constructorFunc: newCryostatController, + } + + c.commonTests() +}) + +func newCryostatController(config *controllers.ReconcilerConfig) controllers.ReconcilerInterface { + return controllers.NewCryostatReconciler(config) +} diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index 5acc780cc..fa58c680b 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -77,6 +77,11 @@ type ReconcilerConfig struct { common.ReconcilerTLS } +type ReconcilerInterface interface { // TODO rename + reconcile.Reconciler + GetConfig() *ReconcilerConfig +} + type Reconciler struct { *ReconcilerConfig } diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index 7713db0a4..3e17aa738 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -72,14 +72,21 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" ) +type controllerTest struct { + clusterScoped bool + constructorFunc func(*controllers.ReconcilerConfig) controllers.ReconcilerInterface +} + type cryostatTestInput struct { - controller *controllers.CryostatReconciler + controller controllers.ReconcilerInterface objs []runtime.Object test.TestReconcilerConfig *test.TestResources } -var CommonJustBeforeEach = func(t *cryostatTestInput) { +func (c *controllerTest) commonTests() { + var t *cryostatTestInput + JustBeforeEach(func() { logger := zap.New() logf.SetLogger(logger) @@ -90,7 +97,7 @@ var CommonJustBeforeEach = func(t *cryostatTestInput) { err := test.SetCreationTimestamp(t.objs...) Expect(err).ToNot(HaveOccurred()) t.Client = fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(t.objs...).Build() - t.controller = controllers.NewCryostatReconciler(&controllers.ReconcilerConfig{ + t.controller = c.constructorFunc(&controllers.ReconcilerConfig{ Client: test.NewClientWithTimestamp(test.NewTestClient(t.Client, t.TestResources)), Scheme: s, IsOpenShift: t.OpenShift, @@ -100,31 +107,27 @@ var CommonJustBeforeEach = func(t *cryostatTestInput) { ReconcilerTLS: test.NewTestReconcilerTLS(&t.TestReconcilerConfig), }) }) -} -var CommonBeforeEach = func(t *cryostatTestInput) { BeforeEach(func() { - input := cryostatTestInput{ + t = &cryostatTestInput{ TestReconcilerConfig: test.TestReconcilerConfig{ GeneratedPasswords: []string{"grafana", "credentials_database", "jmx", "keystore"}, }, TestResources: &test.TestResources{ - Name: "cryostat", - Namespace: "test", - TLS: true, - ExternalTLS: true, - OpenShift: true, + Name: "cryostat", + Namespace: "test", + TLS: true, + ExternalTLS: true, + OpenShift: true, + ClusterScoped: c.clusterScoped, }, } - input.objs = []runtime.Object{ - input.NewNamespace(), - input.NewApiServer(), + t.objs = []runtime.Object{ + t.NewNamespace(), + t.NewApiServer(), } - *t = input }) -} -var CommonSpecs = func(t *cryostatTestInput) { Describe("reconciling a request in OpenShift", func() { expectSuccessful := func() { It("should create certificates", func() { @@ -220,18 +223,26 @@ var CommonSpecs = func(t *cryostatTestInput) { expectSuccessful() }) Context("with multiple namespaces", func() { + // Use different names as well for cluster-scoped case + names := []string{"cryostat-one", "cryostat-two"} namespaces := []string{"test-one", "test-two"} BeforeEach(func() { - for _, ns := range namespaces { - t.Namespace = ns + // Sanity check for test + Expect(names).To(HaveLen(len(namespaces))) + for i := range namespaces { + t.Name = names[i] + t.Namespace = namespaces[i] t.objs = append(t.objs, t.NewNamespace(), t.NewCryostat().Instance) } }) - for _, ns := range namespaces { - ns := ns // capture value + for i := range namespaces { + // capture values for closure + name := names[i] + ns := namespaces[i] Context(fmt.Sprintf("successfully creates required resources in namespace %s", ns), func() { BeforeEach(func() { + t.Name = name t.Namespace = ns }) @@ -295,8 +306,7 @@ var CommonSpecs = func(t *cryostatTestInput) { }) Context("Cryostat does not exist", func() { It("should do nothing", func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "does-not-exist", Namespace: t.Namespace}} - result, err := t.controller.Reconcile(context.Background(), req) + result, err := t.reconcileWithName("does-not-exist") Expect(err).ToNot(HaveOccurred()) Expect(result).To(Equal(reconcile.Result{})) }) @@ -950,16 +960,15 @@ var CommonSpecs = func(t *cryostatTestInput) { field.Forbidden(field.NewPath("spec"), "test error"), }) t.Client = test.NewClientWithUpdateError(t.Client, oldPVC, invalidErr) - t.controller.Client = t.Client + t.controller.GetConfig().Client = t.Client // Expect an Invalid status error after reconciling - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: t.Name, Namespace: t.Namespace}} - _, err := t.controller.Reconcile(context.Background(), req) + _, err := t.reconcile() Expect(err).To(HaveOccurred()) Expect(kerrors.IsInvalid(err)).To(BeTrue()) }) It("should emit a PersistentVolumeClaimInvalid event", func() { - recorder := t.controller.EventRecorder.(*record.FakeRecorder) + recorder := t.controller.GetConfig().EventRecorder.(*record.FakeRecorder) var eventMsg string Expect(recorder.Events).To(Receive(&eventMsg)) Expect(eventMsg).To(ContainSubstring("PersistentVolumeClaimInvalid")) @@ -1356,19 +1365,18 @@ var CommonSpecs = func(t *cryostatTestInput) { Context("cert-manager missing", func() { JustBeforeEach(func() { // Replace with an empty RESTMapper - t.controller.RESTMapper = meta.NewDefaultRESTMapper([]schema.GroupVersion{}) + t.controller.GetConfig().RESTMapper = meta.NewDefaultRESTMapper([]schema.GroupVersion{}) }) Context("and enabled", func() { BeforeEach(func() { t.objs = append(t.objs, t.NewCryostat().Instance) }) JustBeforeEach(func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: t.Name, Namespace: t.Namespace}} - _, err := t.controller.Reconcile(context.Background(), req) + _, err := t.reconcile() Expect(err).To(HaveOccurred()) }) It("should emit a CertManagerUnavailable Event", func() { - recorder := t.controller.EventRecorder.(*record.FakeRecorder) + recorder := t.controller.GetConfig().EventRecorder.(*record.FakeRecorder) var eventMsg string Expect(recorder.Events).To(Receive(&eventMsg)) Expect(eventMsg).To(ContainSubstring("CertManagerUnavailable")) @@ -1384,12 +1392,11 @@ var CommonSpecs = func(t *cryostatTestInput) { t.objs = append(t.objs, t.NewCryostatCertManagerDisabled().Instance) }) JustBeforeEach(func() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: t.Name, Namespace: t.Namespace}} - _, err := t.controller.Reconcile(context.Background(), req) + _, err := t.reconcile() Expect(err).ToNot(HaveOccurred()) }) It("should not emit a CertManagerUnavailable Event", func() { - recorder := t.controller.EventRecorder.(*record.FakeRecorder) + recorder := t.controller.GetConfig().EventRecorder.(*record.FakeRecorder) Expect(recorder.Events).ToNot(Receive()) }) It("should set TLSSetupComplete Condition", func() { @@ -1954,9 +1961,8 @@ func (t *cryostatTestInput) checkConditionAbsent(condType operatorv1beta1.Cryost } func (t *cryostatTestInput) reconcileCryostatFully() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: t.Name, Namespace: t.Namespace}} Eventually(func() reconcile.Result { - result, err := t.controller.Reconcile(context.Background(), req) + result, err := t.reconcile() Expect(err).ToNot(HaveOccurred()) return result }).WithTimeout(time.Minute).WithPolling(time.Millisecond).Should(Equal(reconcile.Result{})) @@ -1989,8 +1995,7 @@ func (t *cryostatTestInput) expectNoCryostat() { } func (t *cryostatTestInput) expectCertificates() { - req := reconcile.Request{NamespacedName: types.NamespacedName{Name: t.Name, Namespace: t.Namespace}} - result, err := t.controller.Reconcile(context.Background(), req) + result, err := t.reconcile() Expect(err).ToNot(HaveOccurred()) Expect(result).To(Equal(reconcile.Result{RequeueAfter: 5 * time.Second})) t.checkCertificates() @@ -2733,3 +2738,16 @@ func (t *cryostatTestInput) updateCryostatInstance(cr *model.CryostatInstance) { err := t.Client.Update(context.Background(), cr.Instance) Expect(err).ToNot(HaveOccurred()) } + +func (t *cryostatTestInput) reconcile() (reconcile.Result, error) { + return t.reconcileWithName(t.Name) +} + +func (t *cryostatTestInput) reconcileWithName(name string) (reconcile.Result, error) { + nsName := types.NamespacedName{Name: name} + if !t.ClusterScoped { + nsName.Namespace = t.Namespace + } + req := reconcile.Request{NamespacedName: nsName} + return t.controller.Reconcile(context.Background(), req) +} diff --git a/internal/test/resources.go b/internal/test/resources.go index a1cdfcf9a..01c67dec0 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -114,6 +114,9 @@ func (r *TestResources) NewCryostat() *model.CryostatInstance { } if r.ClusterScoped { cr := &operatorv1beta1.ClusterCryostat{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterCryostat", + }, ObjectMeta: metav1.ObjectMeta{ Name: r.Name, }, @@ -129,6 +132,9 @@ func (r *TestResources) NewCryostat() *model.CryostatInstance { return r.ConvertClusterToModel(cr) } else { cr := &operatorv1beta1.Cryostat{ + TypeMeta: metav1.TypeMeta{ + Kind: "Cryostat", + }, ObjectMeta: metav1.ObjectMeta{ Name: r.Name, Namespace: r.Namespace, From ab7ac0f2ac11d0731a2132a2d8644c1269ad265c Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Wed, 25 Jan 2023 19:21:24 -0500 Subject: [PATCH 06/21] Implement RBAC per target namespace --- api/v1beta1/cluster_cryostat_types.go | 6 +- api/v1beta1/zz_generated.deepcopy.go | 5 + ...operator.cryostat.io_clustercryostats.yaml | 6 + .../cluster_cryostat_controller_test.go | 77 +++++++++++ internal/controllers/cryostat_controller.go | 4 + internal/controllers/model/instance.go | 37 +++-- internal/controllers/rbac.go | 107 +++++++++++++-- internal/controllers/reconciler.go | 10 +- internal/controllers/reconciler_test.go | 128 ++++++++++-------- internal/test/resources.go | 113 ++++++++-------- 10 files changed, 356 insertions(+), 137 deletions(-) diff --git a/api/v1beta1/cluster_cryostat_types.go b/api/v1beta1/cluster_cryostat_types.go index 23037bb88..96aa1919d 100644 --- a/api/v1beta1/cluster_cryostat_types.go +++ b/api/v1beta1/cluster_cryostat_types.go @@ -54,7 +54,11 @@ type ClusterCryostatSpec struct { // ClusterCryostatStatus defines the observed state of ClusterCryostat. type ClusterCryostatStatus struct { - CryostatStatus `json:",inline"` + // List of namespaces that Cryostat has been configured + // and authorized to access and profile. + // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} + TargetNamespaces []string `json:"targetNamespaces,omitempty"` + CryostatStatus `json:",inline"` } // +kubebuilder:object:root=true diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 9238b4179..4786f88da 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -196,6 +196,11 @@ func (in *ClusterCryostatSpec) DeepCopy() *ClusterCryostatSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterCryostatStatus) DeepCopyInto(out *ClusterCryostatStatus) { *out = *in + if in.TargetNamespaces != nil { + in, out := &in.TargetNamespaces, &out.TargetNamespaces + *out = make([]string, len(*in)) + copy(*out, *in) + } in.CryostatStatus.DeepCopyInto(&out.CryostatStatus) } diff --git a/config/crd/bases/operator.cryostat.io_clustercryostats.yaml b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml index 4f24613e3..55e805e62 100644 --- a/config/crd/bases/operator.cryostat.io_clustercryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml @@ -4500,6 +4500,12 @@ spec: grafanaSecret: description: Name of the Secret containing the generated Grafana credentials. type: string + targetNamespaces: + description: List of namespaces that Cryostat has been configured + and authorized to access and profile. + items: + type: string + type: array required: - applicationUrl type: object diff --git a/internal/controllers/cluster_cryostat_controller_test.go b/internal/controllers/cluster_cryostat_controller_test.go index 607cde006..6f2a1895d 100644 --- a/internal/controllers/cluster_cryostat_controller_test.go +++ b/internal/controllers/cluster_cryostat_controller_test.go @@ -37,8 +37,13 @@ package controllers_test import ( + "context" + "github.com/cryostatio/cryostat-operator/internal/controllers" . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" ) var _ = Describe("ClusterCryostatController", func() { @@ -48,8 +53,80 @@ var _ = Describe("ClusterCryostatController", func() { } c.commonTests() + + Context("reconciling a multi-namespace request", func() { + var t *cryostatTestInput + targetNamespaces := []string{"multi-test-one", "multi-test-two"} + + BeforeEach(func() { + t = c.commonBeforeEach() + t.TargetNamespaces = targetNamespaces + t.objs = append(t.objs, t.NewCryostat().Instance) + // Create Namespaces + saveNS := t.Namespace + for _, ns := range targetNamespaces { + t.Namespace = ns + t.objs = append(t.objs, t.NewNamespace()) + } + t.Namespace = saveNS + }) + + JustBeforeEach(func() { + c.commonJustBeforeEach(t) + }) + + It("should create RBAC in each namespace", func() { + t.expectRBAC() + }) + + It("should update the target namespaces in Status", func() { + t.expectTargetNamespaces() + }) + + Context("with removed target namespaces", func() { + BeforeEach(func() { + t = c.commonBeforeEach() + // Begin with RBAC set up for two namespaces, + // and remove the second namespace from the spec + t.TargetNamespaces = targetNamespaces[:1] + cr := t.NewCryostat() + *cr.TargetNamespaceStatus = targetNamespaces + t.objs = append(t.objs, cr.Instance, + t.NewRole(targetNamespaces[0]), t.NewRoleBinding(targetNamespaces[0]), + t.NewRole(targetNamespaces[1]), t.NewRoleBinding(targetNamespaces[1])) + }) + + It("leave RBAC for the first namespace", func() { + t.expectRBAC() + }) + + It("should remove RBAC from the second namespace", func() { + t.reconcileCryostatFully() + + role := t.NewRole(targetNamespaces[1]) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, role) + Expect(err).ToNot(BeNil()) + Expect(errors.IsNotFound(err)).To(BeTrue()) + + binding := t.NewRoleBinding(targetNamespaces[1]) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: binding.Name, Namespace: binding.Namespace}, binding) + Expect(err).ToNot(BeNil()) + Expect(errors.IsNotFound(err)).To(BeTrue()) + }) + + It("should update the target namespaces in Status", func() { + t.expectTargetNamespaces() + }) + }) + }) }) +func (t *cryostatTestInput) expectTargetNamespaces() { + t.reconcileCryostatFully() + cr := t.getCryostatInstance() + Expect(*cr.TargetNamespaceStatus).To(ConsistOf(t.TargetNamespaces)) +} + func newClusterCryostatController(config *controllers.ReconcilerConfig) controllers.ReconcilerInterface { return controllers.NewClusterCryostatReconciler(config) } diff --git a/internal/controllers/cryostat_controller.go b/internal/controllers/cryostat_controller.go index c09080841..776b89716 100644 --- a/internal/controllers/cryostat_controller.go +++ b/internal/controllers/cryostat_controller.go @@ -98,6 +98,9 @@ func (r *CryostatReconciler) Reconcile(ctx context.Context, request ctrl.Request return reconcile.Result{}, err } + // TODO Consider potential name conflicts. + // For namespaced, look up cluster-scoped with same name. If it exists, check the list of target namespaces. + // If there's a match, don't process the CR. Emit an event warning the user of the conflict. instance := model.FromCryostat(cr) return r.delegate.ReconcileCryostat(ctx, instance) } @@ -107,6 +110,7 @@ func (r *CryostatReconciler) SetupWithManager(mgr ctrl.Manager) error { c := ctrl.NewControllerManagedBy(mgr). For(&operatorv1beta1.Cryostat{}) + // TODO refactor to share between controllers // Watch for changes to secondary resources and requeue the owner Cryostat resources := []client.Object{&appsv1.Deployment{}, &corev1.Service{}, &corev1.Secret{}, &corev1.PersistentVolumeClaim{}, &corev1.ServiceAccount{}, &rbacv1.Role{}, &rbacv1.RoleBinding{}, &netv1.Ingress{}} diff --git a/internal/controllers/model/instance.go b/internal/controllers/model/instance.go index 2af4089fb..dfc715ddb 100644 --- a/internal/controllers/model/instance.go +++ b/internal/controllers/model/instance.go @@ -41,22 +41,37 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +// CryostatInstance is an abstraction to work with both cluster-scoped +// and namespace-scoped Cryostat CRDs. type CryostatInstance struct { - Name string + // Name of the Cryostat CR. + Name string + // Namespace to install Cryostat into. For Cryostat, this comes from the + // CR's namespace. For ClusterCryostat, this comes from spec.InstallNamespace. InstallNamespace string + // Namespaces that Cryostat should look for targets. For Cryostat, this is + // always the CR's namespace. For ClusterCryostat, this comes from spec.TargetNamespaces. TargetNamespaces []string - - Spec *operatorv1beta1.CryostatSpec + // Namespaces that the operator has successfully set up RBAC for Cryostat to monitor targets + // in that namespace. For Cryostat, this is always the CR's namespace. + // For ClusterCryostat, this is a reference to status.TargetNamespaces. + TargetNamespaceStatus *[]string + // Reference to the common Spec properties to both CRD types. + Spec *operatorv1beta1.CryostatSpec + // Reference to the common Status properties to both CRD types. Status *operatorv1beta1.CryostatStatus - + // The actual CR instance as a generic Kubernetes object. Instance client.Object } +// FromCryostat creates a CryostatInstance from a Cryostat CR func FromCryostat(cr *operatorv1beta1.Cryostat) *CryostatInstance { + targetNS := []string{cr.Namespace} return &CryostatInstance{ - Name: cr.Name, - InstallNamespace: cr.Namespace, - TargetNamespaces: []string{cr.Namespace}, + Name: cr.Name, + InstallNamespace: cr.Namespace, + TargetNamespaces: targetNS, + TargetNamespaceStatus: &targetNS, Spec: &cr.Spec, Status: &cr.Status, @@ -65,6 +80,7 @@ func FromCryostat(cr *operatorv1beta1.Cryostat) *CryostatInstance { } } +// FromClusterCryostat creates a CryostatInstance from a ClusterCryostat CR func FromClusterCryostat(cr *operatorv1beta1.ClusterCryostat) *CryostatInstance { // If target namespaces aren't explicitly listed, use the install namespace targetNamespaces := cr.Spec.TargetNamespaces @@ -72,9 +88,10 @@ func FromClusterCryostat(cr *operatorv1beta1.ClusterCryostat) *CryostatInstance targetNamespaces = []string{cr.Spec.InstallNamespace} } return &CryostatInstance{ - Name: cr.Name, - InstallNamespace: cr.Spec.InstallNamespace, - TargetNamespaces: targetNamespaces, + Name: cr.Name, + InstallNamespace: cr.Spec.InstallNamespace, + TargetNamespaces: targetNamespaces, + TargetNamespaceStatus: &cr.Status.TargetNamespaces, Spec: &cr.Spec.CryostatSpec, Status: &cr.Status.CryostatStatus, diff --git a/internal/controllers/rbac.go b/internal/controllers/rbac.go index 17d19b9b5..7d4969e8f 100644 --- a/internal/controllers/rbac.go +++ b/internal/controllers/rbac.go @@ -113,17 +113,18 @@ func (r *Reconciler) reconcileServiceAccount(ctx context.Context, cr *model.Cryo return r.createOrUpdateServiceAccount(ctx, sa, cr.Instance, labels, annotations) } -func newRole(cr *model.CryostatInstance) *rbacv1.Role { +func newRole(cr *model.CryostatInstance, namespace string) *rbacv1.Role { return &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.InstallNamespace, + Namespace: namespace, }, } } func (r *Reconciler) reconcileRole(ctx context.Context, cr *model.CryostatInstance) error { - role := newRole(cr) + // TODO convert to ClusterRole? Needs to be separate from existing one used by ClusterRoleBinding. + // If we do, we should delete existing Roles (check for ownership before deleting). rules := []rbacv1.PolicyRule{ { Verbs: []string{"get", "list", "watch"}, @@ -151,17 +152,36 @@ func (r *Reconciler) reconcileRole(ctx context.Context, cr *model.CryostatInstan Resources: []string{"routes"}, }, } - return r.createOrUpdateRole(ctx, role, cr.Instance, rules) + + // Create a Role in each target namespace + for _, ns := range cr.TargetNamespaces { + role := newRole(cr, ns) + err := r.createOrUpdateRole(ctx, role, cr.Instance, rules) + if err != nil { + return err + } + } + // Delete any Roles in target namespaces that are no longer requested + for _, ns := range toDelete(cr) { + role := newRole(cr, ns) + err := r.deleteRole(ctx, role) + if err != nil { + return err + } + } + return nil } -func (r *Reconciler) reconcileRoleBinding(ctx context.Context, cr *model.CryostatInstance) error { - binding := &rbacv1.RoleBinding{ +func newRoleBinding(cr *model.CryostatInstance, namespace string) *rbacv1.RoleBinding { + return &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: cr.InstallNamespace, + Namespace: namespace, }, } +} +func (r *Reconciler) reconcileRoleBinding(ctx context.Context, cr *model.CryostatInstance) error { sa := newServiceAccount(cr) subjects := []rbacv1.Subject{ { @@ -171,13 +191,29 @@ func (r *Reconciler) reconcileRoleBinding(ctx context.Context, cr *model.Cryosta }, } - roleRef := &rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: newRole(cr).Name, + // Create a RoleBinding in each target namespace + for _, ns := range cr.TargetNamespaces { + binding := newRoleBinding(cr, ns) + roleRef := &rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: newRole(cr, ns).Name, + } + err := r.createOrUpdateRoleBinding(ctx, binding, cr.Instance, subjects, roleRef) + if err != nil { + return err + } + } + // Delete any RoleBindings in target namespaces that are no longer requested + for _, ns := range toDelete(cr) { + binding := newRoleBinding(cr, ns) + err := r.deleteRoleBinding(ctx, binding) + if err != nil { + return err + } } - return r.createOrUpdateRoleBinding(ctx, binding, cr.Instance, subjects, roleRef) + return nil } func newClusterRoleBinding(cr *model.CryostatInstance) *rbacv1.ClusterRoleBinding { @@ -259,6 +295,20 @@ func (r *Reconciler) createOrUpdateRole(ctx context.Context, role *rbacv1.Role, return nil } +func (r *Reconciler) deleteRole(ctx context.Context, role *rbacv1.Role) error { // TODO refactor to reduce duplication + err := r.Client.Delete(ctx, role) + if err != nil { + if kerrors.IsNotFound(err) { + r.Log.Info("No role to delete", "name", role.Name, "namespace", role.Namespace) + return nil + } + r.Log.Error(err, "Could not delete role", "name", role.Name, "namespace", role.Namespace) + return err + } + r.Log.Info("Role deleted", "name", role.Name, "namespace", role.Namespace) + return nil +} + func (r *Reconciler) createOrUpdateRoleBinding(ctx context.Context, binding *rbacv1.RoleBinding, owner metav1.Object, subjects []rbacv1.Subject, roleRef *rbacv1.RoleRef) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, binding, func() error { @@ -280,6 +330,20 @@ func (r *Reconciler) createOrUpdateRoleBinding(ctx context.Context, binding *rba return nil } +func (r *Reconciler) deleteRoleBinding(ctx context.Context, binding *rbacv1.RoleBinding) error { + err := r.Client.Delete(ctx, binding) + if err != nil { + if kerrors.IsNotFound(err) { + r.Log.Info("No role binding to delete", "name", binding.Name, "namespace", binding.Namespace) + return nil + } + r.Log.Error(err, "Could not delete role binding", "name", binding.Name, "namespace", binding.Namespace) + return err + } + r.Log.Info("Role Binding deleted", "name", binding.Name, "namespace", binding.Namespace) + return nil +} + func (r *Reconciler) createOrUpdateClusterRoleBinding(ctx context.Context, binding *rbacv1.ClusterRoleBinding, owner metav1.Object, subjects []rbacv1.Subject, roleRef *rbacv1.RoleRef) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, binding, func() error { @@ -312,3 +376,22 @@ func (r *Reconciler) deleteClusterRoleBinding(ctx context.Context, cr *model.Cry r.Log.Info("deleted ClusterRoleBinding", "bindingName", clusterBinding.Name) return nil } + +func toDelete(cr *model.CryostatInstance) []string { + toDelete := []string{} + for _, ns := range *cr.TargetNamespaceStatus { + if !containsNamespace(cr.TargetNamespaces, ns) { + toDelete = append(toDelete, ns) + } + } + return toDelete +} + +func containsNamespace(namespaces []string, namespace string) bool { + for _, ns := range namespaces { + if ns == namespace { + return true + } + } + return false +} diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index fa58c680b..f8bb851ba 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -244,12 +244,14 @@ func (r *Reconciler) ReconcileCryostat(ctx context.Context, cr *model.CryostatIn return reconcile.Result{}, err } + // Update CR Status if serviceSpecs.CoreURL != nil { cr.Status.ApplicationURL = serviceSpecs.CoreURL.String() - err = r.Client.Status().Update(ctx, cr.Instance) - if err != nil { - return reconcile.Result{}, err - } + } + *cr.TargetNamespaceStatus = cr.TargetNamespaces + err = r.Client.Status().Update(ctx, cr.Instance) + if err != nil { + return reconcile.Result{}, err } // OpenShift-specific diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index 3e17aa738..522ba85b5 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -84,48 +84,58 @@ type cryostatTestInput struct { *test.TestResources } +func (c *controllerTest) commonBeforeEach() *cryostatTestInput { + t := &cryostatTestInput{ + TestReconcilerConfig: test.TestReconcilerConfig{ + GeneratedPasswords: []string{"grafana", "credentials_database", "jmx", "keystore"}, + }, + TestResources: &test.TestResources{ + Name: "cryostat", + Namespace: "test", + TLS: true, + ExternalTLS: true, + OpenShift: true, + ClusterScoped: c.clusterScoped, + }, + } + t.objs = []runtime.Object{ + t.NewNamespace(), + t.NewApiServer(), + } + return t +} + +func (c *controllerTest) commonJustBeforeEach(t *cryostatTestInput) { + logger := zap.New() + logf.SetLogger(logger) + s := test.NewTestScheme() + + // Set a CreationTimestamp for created objects to match a real API server + // TODO When using envtest instead of fake client, this is probably no longer needed + err := test.SetCreationTimestamp(t.objs...) + Expect(err).ToNot(HaveOccurred()) + t.Client = fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(t.objs...).Build() + t.controller = c.constructorFunc(&controllers.ReconcilerConfig{ + Client: test.NewClientWithTimestamp(test.NewTestClient(t.Client, t.TestResources)), + Scheme: s, + IsOpenShift: t.OpenShift, + EventRecorder: record.NewFakeRecorder(1024), + RESTMapper: test.NewTESTRESTMapper(), + Log: logger, + ReconcilerTLS: test.NewTestReconcilerTLS(&t.TestReconcilerConfig), + }) +} + func (c *controllerTest) commonTests() { var t *cryostatTestInput JustBeforeEach(func() { - logger := zap.New() - logf.SetLogger(logger) - s := test.NewTestScheme() - - // Set a CreationTimestamp for created objects to match a real API server - // TODO When using envtest instead of fake client, this is probably no longer needed - err := test.SetCreationTimestamp(t.objs...) - Expect(err).ToNot(HaveOccurred()) - t.Client = fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(t.objs...).Build() - t.controller = c.constructorFunc(&controllers.ReconcilerConfig{ - Client: test.NewClientWithTimestamp(test.NewTestClient(t.Client, t.TestResources)), - Scheme: s, - IsOpenShift: t.OpenShift, - EventRecorder: record.NewFakeRecorder(1024), - RESTMapper: test.NewTESTRESTMapper(), - Log: logger, - ReconcilerTLS: test.NewTestReconcilerTLS(&t.TestReconcilerConfig), - }) + c.commonJustBeforeEach(t) }) BeforeEach(func() { - t = &cryostatTestInput{ - TestReconcilerConfig: test.TestReconcilerConfig{ - GeneratedPasswords: []string{"grafana", "credentials_database", "jmx", "keystore"}, - }, - TestResources: &test.TestResources{ - Name: "cryostat", - Namespace: "test", - TLS: true, - ExternalTLS: true, - OpenShift: true, - ClusterScoped: c.clusterScoped, - }, - } - t.objs = []runtime.Object{ - t.NewNamespace(), - t.NewApiServer(), - } + t = c.commonBeforeEach() + t.TargetNamespaces = []string{t.Namespace} }) Describe("reconciling a request in OpenShift", func() { @@ -232,6 +242,7 @@ func (c *controllerTest) commonTests() { for i := range namespaces { t.Name = names[i] t.Namespace = namespaces[i] + t.TargetNamespaces = []string{t.Namespace} t.objs = append(t.objs, t.NewNamespace(), t.NewCryostat().Instance) } }) @@ -244,6 +255,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { t.Name = name t.Namespace = ns + t.TargetNamespaces = []string{t.Namespace} }) expectSuccessful() @@ -394,7 +406,7 @@ func (c *controllerTest) commonTests() { var oldRole *rbacv1.Role BeforeEach(func() { cr = t.NewCryostat() - oldRole = t.OtherRole() + oldRole = t.OtherRole(t.Namespace) t.objs = append(t.objs, cr.Instance, oldRole) }) It("should update the Role", func() { @@ -411,7 +423,7 @@ func (c *controllerTest) commonTests() { Expect(role.Annotations).To(Equal(oldRole.Annotations)) // Rules should be fully replaced - Expect(role.Rules).To(Equal(t.NewRole().Rules)) + Expect(role.Rules).To(Equal(t.NewRole(t.Namespace).Rules)) }) }) Context("with an existing Role Binding", func() { @@ -419,7 +431,7 @@ func (c *controllerTest) commonTests() { var oldBinding *rbacv1.RoleBinding BeforeEach(func() { cr = t.NewCryostat() - oldBinding = t.OtherRoleBinding() + oldBinding = t.OtherRoleBinding(t.Namespace) t.objs = append(t.objs, cr.Instance, oldBinding) }) It("should update the Role Binding", func() { @@ -436,7 +448,7 @@ func (c *controllerTest) commonTests() { Expect(binding.Annotations).To(Equal(oldBinding.Annotations)) // Subjects and RoleRef should be fully replaced - expected := t.NewRoleBinding() + expected := t.NewRoleBinding(t.Namespace) Expect(binding.Subjects).To(Equal(expected.Subjects)) Expect(binding.RoleRef).To(Equal(expected.RoleRef)) }) @@ -1848,7 +1860,7 @@ func (c *controllerTest) commonTests() { var oldRole *rbacv1.Role BeforeEach(func() { cr = t.NewCryostat() - oldRole = t.OtherRole() + oldRole = t.OtherRole(t.Namespace) t.objs = append(t.objs, cr.Instance, oldRole) }) It("should update the Role", func() { @@ -1865,7 +1877,7 @@ func (c *controllerTest) commonTests() { Expect(role.Annotations).To(Equal(oldRole.Annotations)) // Rules should be fully replaced - Expect(role.Rules).To(Equal(t.NewRole().Rules)) + Expect(role.Rules).To(Equal(t.NewRole(t.Namespace).Rules)) }) }) Context("with an existing Role Binding", func() { @@ -1873,7 +1885,7 @@ func (c *controllerTest) commonTests() { var oldBinding *rbacv1.RoleBinding BeforeEach(func() { cr = t.NewCryostat() - oldBinding = t.OtherRoleBinding() + oldBinding = t.OtherRoleBinding(t.Namespace) t.objs = append(t.objs, cr.Instance, oldBinding) }) It("should update the Role Binding", func() { @@ -1890,7 +1902,7 @@ func (c *controllerTest) commonTests() { Expect(binding.Annotations).To(Equal(oldBinding.Annotations)) // Subjects and RoleRef should be fully replaced - expected := t.NewRoleBinding() + expected := t.NewRoleBinding(t.Namespace) Expect(binding.Subjects).To(Equal(expected.Subjects)) Expect(binding.RoleRef).To(Equal(expected.RoleRef)) }) @@ -2054,20 +2066,24 @@ func (t *cryostatTestInput) expectRBAC() { Expect(sa.ImagePullSecrets).To(Equal(expectedSA.ImagePullSecrets)) Expect(sa.AutomountServiceAccountToken).To(Equal(expectedSA.AutomountServiceAccountToken)) - role := &rbacv1.Role{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, role) - Expect(err).ToNot(HaveOccurred()) - expectedRole := t.NewRole() - t.checkMetadata(role, expectedRole) - Expect(role.Rules).To(Equal(expectedRole.Rules)) + // Check for Role and RoleBinding in each target namespace + Expect(t.TargetNamespaces).ToNot(BeEmpty()) // Sanity check for tests + for _, ns := range t.TargetNamespaces { + role := &rbacv1.Role{} + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: ns}, role) + Expect(err).ToNot(HaveOccurred()) + expectedRole := t.NewRole(ns) + t.checkMetadata(role, expectedRole) + Expect(role.Rules).To(Equal(expectedRole.Rules)) - binding := &rbacv1.RoleBinding{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, binding) - Expect(err).ToNot(HaveOccurred()) - expectedBinding := t.NewRoleBinding() - t.checkMetadata(binding, expectedBinding) - Expect(binding.Subjects).To(Equal(expectedBinding.Subjects)) - Expect(binding.RoleRef).To(Equal(expectedBinding.RoleRef)) + binding := &rbacv1.RoleBinding{} + err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: ns}, binding) + Expect(err).ToNot(HaveOccurred()) + expectedBinding := t.NewRoleBinding(ns) + t.checkMetadata(binding, expectedBinding) + Expect(binding.Subjects).To(Equal(expectedBinding.Subjects)) + Expect(binding.RoleRef).To(Equal(expectedBinding.RoleRef)) + } expectedClusterBinding := t.NewClusterRoleBinding() clusterBinding := &rbacv1.ClusterRoleBinding{} diff --git a/internal/test/resources.go b/internal/test/resources.go index 01c67dec0..9288ad740 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -105,6 +105,38 @@ func NewTESTRESTMapper() meta.RESTMapper { // FIXME need to split this up, make private newCryostat that returns API and use either a copy or actual model.FromCryostat throughout func (r *TestResources) NewCryostat() *model.CryostatInstance { + + if r.ClusterScoped { + return r.ConvertClusterToModel(r.newClusterCryostat()) + } else { + return r.ConvertNamespacedToModel(r.newCryostat()) + } +} + +func (r *TestResources) newClusterCryostat() *operatorv1beta1.ClusterCryostat { + return &operatorv1beta1.ClusterCryostat{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.Name, + }, + Spec: operatorv1beta1.ClusterCryostatSpec{ + InstallNamespace: r.Namespace, + TargetNamespaces: r.TargetNamespaces, + CryostatSpec: r.newCryostatSpec(), + }, + } +} + +func (r *TestResources) newCryostat() *operatorv1beta1.Cryostat { + return &operatorv1beta1.Cryostat{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.Name, + Namespace: r.Namespace, + }, + Spec: r.newCryostatSpec(), + } +} + +func (r *TestResources) newCryostatSpec() operatorv1beta1.CryostatSpec { certManager := true var reportOptions *operatorv1beta1.ReportConfiguration if r.ReportReplicas > 0 { @@ -112,62 +144,35 @@ func (r *TestResources) NewCryostat() *model.CryostatInstance { Replicas: r.ReportReplicas, } } - if r.ClusterScoped { - cr := &operatorv1beta1.ClusterCryostat{ - TypeMeta: metav1.TypeMeta{ - Kind: "ClusterCryostat", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: r.Name, - }, - Spec: operatorv1beta1.ClusterCryostatSpec{ - InstallNamespace: r.Namespace, - CryostatSpec: operatorv1beta1.CryostatSpec{ - Minimal: r.Minimal, - EnableCertManager: &certManager, - ReportOptions: reportOptions, - }, - }, - } - return r.ConvertClusterToModel(cr) - } else { - cr := &operatorv1beta1.Cryostat{ - TypeMeta: metav1.TypeMeta{ - Kind: "Cryostat", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: r.Name, - Namespace: r.Namespace, - }, - Spec: operatorv1beta1.CryostatSpec{ - Minimal: r.Minimal, - EnableCertManager: &certManager, - ReportOptions: reportOptions, - }, - } - return r.ConvertNamespacedToModel(cr) + return operatorv1beta1.CryostatSpec{ + Minimal: r.Minimal, + EnableCertManager: &certManager, + ReportOptions: reportOptions, } } func (r *TestResources) ConvertNamespacedToModel(cr *operatorv1beta1.Cryostat) *model.CryostatInstance { + targetNS := []string{cr.Namespace} return &model.CryostatInstance{ - Name: cr.Name, - InstallNamespace: cr.Namespace, - TargetNamespaces: []string{cr.Namespace}, - Spec: &cr.Spec, - Status: &cr.Status, - Instance: cr, + Name: cr.Name, + InstallNamespace: cr.Namespace, + TargetNamespaces: targetNS, + TargetNamespaceStatus: &targetNS, + Spec: &cr.Spec, + Status: &cr.Status, + Instance: cr, } } func (r *TestResources) ConvertClusterToModel(cr *operatorv1beta1.ClusterCryostat) *model.CryostatInstance { return &model.CryostatInstance{ - Name: cr.Name, - InstallNamespace: cr.Spec.InstallNamespace, - TargetNamespaces: cr.Spec.TargetNamespaces, - Spec: &cr.Spec.CryostatSpec, - Status: &cr.Status.CryostatStatus, - Instance: cr, + Name: cr.Name, + InstallNamespace: cr.Spec.InstallNamespace, + TargetNamespaces: cr.Spec.TargetNamespaces, + TargetNamespaceStatus: &cr.Status.TargetNamespaces, + Spec: &cr.Spec.CryostatSpec, + Status: &cr.Status.CryostatStatus, + Instance: cr, } } @@ -2346,7 +2351,7 @@ func (r *TestResources) OtherServiceAccount() *corev1.ServiceAccount { } } -func (r *TestResources) NewRole() *rbacv1.Role { +func (r *TestResources) NewRole(ns string) *rbacv1.Role { rules := []rbacv1.PolicyRule{ { Verbs: []string{"get", "list", "watch"}, @@ -2377,17 +2382,17 @@ func (r *TestResources) NewRole() *rbacv1.Role { return &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: r.Name, - Namespace: r.Namespace, + Namespace: ns, }, Rules: rules, } } -func (r *TestResources) OtherRole() *rbacv1.Role { +func (r *TestResources) OtherRole(ns string) *rbacv1.Role { return &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: r.Name, - Namespace: r.Namespace, + Namespace: ns, Labels: map[string]string{ "test": "label", }, @@ -2422,11 +2427,11 @@ func (r *TestResources) NewAuthClusterRole() *rbacv1.ClusterRole { } } -func (r *TestResources) NewRoleBinding() *rbacv1.RoleBinding { +func (r *TestResources) NewRoleBinding(ns string) *rbacv1.RoleBinding { return &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: r.Name, - Namespace: r.Namespace, + Namespace: ns, }, Subjects: []rbacv1.Subject{ { @@ -2443,11 +2448,11 @@ func (r *TestResources) NewRoleBinding() *rbacv1.RoleBinding { } } -func (r *TestResources) OtherRoleBinding() *rbacv1.RoleBinding { +func (r *TestResources) OtherRoleBinding(ns string) *rbacv1.RoleBinding { return &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: r.Name, - Namespace: r.Namespace, + Namespace: ns, Labels: map[string]string{ "test": "label", }, From c344798e0c103a9b0094241e662d15103498b8d7 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Wed, 25 Jan 2023 19:37:57 -0500 Subject: [PATCH 07/21] Pass CRYOSTAT_K8S_NAMESPACES to deployment --- api/v1beta1/cluster_cryostat_types.go | 11 ++++---- api/v1beta1/cryostat_types.go | 8 +++--- ...yostat-operator.clusterserviceversion.yaml | 25 +++++++++++++++++-- .../operator.cryostat.io_cryostats.yaml | 8 +++--- ...operator.cryostat.io_clustercryostats.yaml | 10 ++++---- .../bases/operator.cryostat.io_cryostats.yaml | 8 +++--- ...yostat-operator.clusterserviceversion.yaml | 5 ++-- .../cluster_cryostat_controller_test.go | 8 ++++++ .../resource_definitions.go | 5 ++++ internal/controllers/ingresses.go | 1 - internal/controllers/rbac.go | 2 -- internal/test/resources.go | 7 ++++-- 12 files changed, 70 insertions(+), 28 deletions(-) diff --git a/api/v1beta1/cluster_cryostat_types.go b/api/v1beta1/cluster_cryostat_types.go index 96aa1919d..50656f975 100644 --- a/api/v1beta1/cluster_cryostat_types.go +++ b/api/v1beta1/cluster_cryostat_types.go @@ -66,14 +66,15 @@ type ClusterCryostatStatus struct { // +kubebuilder:storageversion // +kubebuilder:resource:path=clustercryostats,scope=Cluster -// ClusterCryostat allows for the installation of a multi-namespace or cluster-wide -// Cryostat. It contains configuration options for controlling the Deployment of -// the Cryostat application and its related components. A Cryostat instance -// must be created to instruct the operator to deploy the Cryostat application. +// ClusterCryostat allows you to install Cryostat for multiple namespaces or cluster-wide. +// It contains configuration options for controlling the Deployment of the Cryostat +// application and its related components. +// A ClusterCryostat or Cryostat instance must be created to instruct the operator +// to deploy the Cryostat application. // +operator-sdk:csv:customresourcedefinitions:resources={{Deployment,v1},{Ingress,v1},{PersistentVolumeClaim,v1},{Secret,v1},{Service,v1},{Route,v1},{ConsoleLink,v1}} // +kubebuilder:printcolumn:name="Application URL",type=string,JSONPath=`.status.applicationUrl` // +kubebuilder:printcolumn:name="Grafana Secret",type=string,JSONPath=`.status.grafanaSecret` -type ClusterCryostat struct { +type ClusterCryostat struct { // TODO add cluster-wide API support metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/api/v1beta1/cryostat_types.go b/api/v1beta1/cryostat_types.go index bff8eab30..93fe57db5 100644 --- a/api/v1beta1/cryostat_types.go +++ b/api/v1beta1/cryostat_types.go @@ -413,9 +413,11 @@ type JmxCacheOptions struct { // +kubebuilder:storageversion // +kubebuilder:resource:path=cryostats,scope=Namespaced -// Cryostat contains configuration options for controlling the Deployment of -// the Cryostat application and its related components. A Cryostat instance -// must be created to instruct the operator to deploy the Cryostat application. +// Cryostat allows you to install Cryostat for a single namespace. +// It contains configuration options for controlling the Deployment of the Cryostat +// application and its related components. +// A ClusterCryostat or Cryostat instance must be created to instruct the operator +// to deploy the Cryostat application. // +operator-sdk:csv:customresourcedefinitions:resources={{Deployment,v1},{Ingress,v1},{PersistentVolumeClaim,v1},{Secret,v1},{Service,v1},{Route,v1},{ConsoleLink,v1}} // +kubebuilder:printcolumn:name="Application URL",type=string,JSONPath=`.status.applicationUrl` // +kubebuilder:printcolumn:name="Grafana Secret",type=string,JSONPath=`.status.grafanaSecret` diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index f517af73d..6f53b135c 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -58,8 +58,9 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: - - description: Cryostat contains configuration options for controlling the Deployment - of the Cryostat application and its related components. A Cryostat instance + - description: Cryostat allows you to install Cryostat for a single namespace. + It contains configuration options for controlling the Deployment of the Cryostat + application and its related components. A ClusterCryostat or Cryostat instance must be created to instruct the operator to deploy the Cryostat application. displayName: Cryostat kind: Cryostat @@ -484,6 +485,26 @@ spec: verbs: - delete - list + - apiGroups: + - operator.cryostat.io + resources: + - clustercryostats + verbs: + - '*' + - apiGroups: + - operator.cryostat.io + resources: + - clustercryostats/finalizers + verbs: + - update + - apiGroups: + - operator.cryostat.io + resources: + - clustercryostats/status + verbs: + - get + - patch + - update - apiGroups: - rbac.authorization.k8s.io resources: diff --git a/bundle/manifests/operator.cryostat.io_cryostats.yaml b/bundle/manifests/operator.cryostat.io_cryostats.yaml index fc193c4f0..e9ae344ad 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -24,9 +24,11 @@ spec: name: v1beta1 schema: openAPIV3Schema: - description: Cryostat contains configuration options for controlling the Deployment - of the Cryostat application and its related components. A Cryostat instance - must be created to instruct the operator to deploy the Cryostat application. + description: Cryostat allows you to install Cryostat for a single namespace. + It contains configuration options for controlling the Deployment of the + Cryostat application and its related components. A ClusterCryostat or Cryostat + instance must be created to instruct the operator to deploy the Cryostat + application. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation diff --git a/config/crd/bases/operator.cryostat.io_clustercryostats.yaml b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml index 55e805e62..bb316a72e 100644 --- a/config/crd/bases/operator.cryostat.io_clustercryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml @@ -25,11 +25,11 @@ spec: name: v1beta1 schema: openAPIV3Schema: - description: ClusterCryostat allows for the installation of a multi-namespace - or cluster-wide Cryostat. It contains configuration options for controlling - the Deployment of the Cryostat application and its related components. A - Cryostat instance must be created to instruct the operator to deploy the - Cryostat application. + description: ClusterCryostat allows you to install Cryostat for multiple namespaces + or cluster-wide. It contains configuration options for controlling the Deployment + of the Cryostat application and its related components. A ClusterCryostat + or Cryostat instance must be created to instruct the operator to deploy + the Cryostat application. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index a5da61ab2..bcc66f4b1 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -25,9 +25,11 @@ spec: name: v1beta1 schema: openAPIV3Schema: - description: Cryostat contains configuration options for controlling the Deployment - of the Cryostat application and its related components. A Cryostat instance - must be created to instruct the operator to deploy the Cryostat application. + description: Cryostat allows you to install Cryostat for a single namespace. + It contains configuration options for controlling the Deployment of the + Cryostat application and its related components. A ClusterCryostat or Cryostat + instance must be created to instruct the operator to deploy the Cryostat + application. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index a4b370408..66d1ecbce 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -46,8 +46,9 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: - - description: Cryostat contains configuration options for controlling the Deployment - of the Cryostat application and its related components. A Cryostat instance + - description: Cryostat allows you to install Cryostat for a single namespace. + It contains configuration options for controlling the Deployment of the Cryostat + application and its related components. A ClusterCryostat or Cryostat instance must be created to instruct the operator to deploy the Cryostat application. displayName: Cryostat kind: Cryostat diff --git a/internal/controllers/cluster_cryostat_controller_test.go b/internal/controllers/cluster_cryostat_controller_test.go index 6f2a1895d..d35b762e5 100644 --- a/internal/controllers/cluster_cryostat_controller_test.go +++ b/internal/controllers/cluster_cryostat_controller_test.go @@ -75,6 +75,10 @@ var _ = Describe("ClusterCryostatController", func() { c.commonJustBeforeEach(t) }) + It("should create the expected main deployment", func() { + t.expectDeployment() + }) + It("should create RBAC in each namespace", func() { t.expectRBAC() }) @@ -96,6 +100,10 @@ var _ = Describe("ClusterCryostatController", func() { t.NewRole(targetNamespaces[1]), t.NewRoleBinding(targetNamespaces[1])) }) + It("should create the expected main deployment", func() { + t.expectDeployment() + }) + It("leave RBAC for the first namespace", func() { t.expectRBAC() }) diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 64393d577..6e9c832f9 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -41,6 +41,7 @@ import ( "net/url" "regexp" "strconv" + "strings" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "github.com/cryostatio/cryostat-operator/internal/constants" @@ -599,6 +600,10 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag Name: "CRYOSTAT_ENABLE_JDP_BROADCAST", Value: "false", }, + { + Name: "CRYOSTAT_K8S_NAMESPACES", + Value: strings.Join(cr.TargetNamespaces, ","), + }, } mounts := []corev1.VolumeMount{ diff --git a/internal/controllers/ingresses.go b/internal/controllers/ingresses.go index 2688cb032..57ec8da41 100644 --- a/internal/controllers/ingresses.go +++ b/internal/controllers/ingresses.go @@ -41,7 +41,6 @@ import ( "fmt" "net/url" - //operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" "github.com/cryostatio/cryostat-operator/internal/controllers/model" diff --git a/internal/controllers/rbac.go b/internal/controllers/rbac.go index 7d4969e8f..f03cf844b 100644 --- a/internal/controllers/rbac.go +++ b/internal/controllers/rbac.go @@ -51,8 +51,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -// TODO Use Spec.TargetNamespaces to set up RBAC - func (r *Reconciler) reconcileRBAC(ctx context.Context, cr *model.CryostatInstance) error { err := r.reconcileServiceAccount(ctx, cr) if err != nil { diff --git a/internal/test/resources.go b/internal/test/resources.go index 9288ad740..bd6bc45f5 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -39,6 +39,7 @@ package test import ( "crypto/sha256" "fmt" + "strings" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "github.com/cryostatio/cryostat-operator/internal/controllers/model" @@ -103,9 +104,7 @@ func NewTESTRESTMapper() meta.RESTMapper { return mapper } -// FIXME need to split this up, make private newCryostat that returns API and use either a copy or actual model.FromCryostat throughout func (r *TestResources) NewCryostat() *model.CryostatInstance { - if r.ClusterScoped { return r.ConvertClusterToModel(r.newClusterCryostat()) } else { @@ -1249,6 +1248,10 @@ func (r *TestResources) NewCoreEnvironmentVariables(reportsUrl string, authProps Name: "CRYOSTAT_TARGET_CACHE_TTL", Value: "10", }, + { + Name: "CRYOSTAT_K8S_NAMESPACES", + Value: strings.Join(r.TargetNamespaces, ","), + }, } if builtInDiscoveryDisabled { From b6322d54244aada19988933e4d8219a85d5a4a74 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Thu, 26 Jan 2023 17:26:11 -0500 Subject: [PATCH 08/21] Fix generated files with operator-sdk --- PROJECT | 18 +- ...stat_types.go => clustercryostat_types.go} | 0 ...yostat-operator.clusterserviceversion.yaml | 398 ++ ...operator.cryostat.io_clustercryostats.yaml | 4521 +++++++++++++++++ config/crd/kustomization.yaml | 3 + .../cainjection_in_clustercryostats.yaml | 7 + .../patches/webhook_in_clustercryostats.yaml | 16 + ...yostat-operator.clusterserviceversion.yaml | 375 ++ config/rbac/clustercryostat_editor_role.yaml | 24 + config/rbac/clustercryostat_viewer_role.yaml | 20 + config/samples/kustomization.yaml | 1 + .../operator_v1beta1_clustercryostat.yaml | 16 + ...oller.go => clustercryostat_controller.go} | 0 ....go => clustercryostat_controller_test.go} | 0 .../resource_definitions/certificates.go | 2 +- .../resource_definitions.go | 2 +- .../{ => controllers}/constants/constants.go | 0 internal/controllers/routes.go | 2 +- internal/controllers/services.go | 2 +- internal/main.go | 12 +- 20 files changed, 5407 insertions(+), 12 deletions(-) rename api/v1beta1/{cluster_cryostat_types.go => clustercryostat_types.go} (100%) create mode 100644 bundle/manifests/operator.cryostat.io_clustercryostats.yaml create mode 100644 config/crd/patches/cainjection_in_clustercryostats.yaml create mode 100644 config/crd/patches/webhook_in_clustercryostats.yaml create mode 100644 config/rbac/clustercryostat_editor_role.yaml create mode 100644 config/rbac/clustercryostat_viewer_role.yaml create mode 100644 config/samples/operator_v1beta1_clustercryostat.yaml rename internal/controllers/{cluster_cryostat_controller.go => clustercryostat_controller.go} (100%) rename internal/controllers/{cluster_cryostat_controller_test.go => clustercryostat_controller_test.go} (100%) rename internal/{ => controllers}/constants/constants.go (100%) diff --git a/PROJECT b/PROJECT index 9c0cf4da3..79b5f32d3 100644 --- a/PROJECT +++ b/PROJECT @@ -1,5 +1,9 @@ domain: cryostat.io -layout: go.kubebuilder.io/v3 +layout: +- go.kubebuilder.io/v3 +plugins: + manifests.sdk.operatorframework.io/v2: {} + scorecard.sdk.operatorframework.io/v2: {} projectName: cryostat-operator repo: github.com/cryostatio/cryostat-operator resources: @@ -12,7 +16,13 @@ resources: kind: Cryostat path: github.com/cryostatio/cryostat-operator/api/v1beta1 version: v1beta1 +- api: + crdVersion: v1 + namespaced: false + controller: true + domain: cryostat.io + group: operator + kind: ClusterCryostat + path: github.com/cryostatio/cryostat-operator/api/v1beta1 + version: v1beta1 version: "3" -plugins: - manifests.sdk.operatorframework.io/v2: {} - scorecard.sdk.operatorframework.io/v2: {} diff --git a/api/v1beta1/cluster_cryostat_types.go b/api/v1beta1/clustercryostat_types.go similarity index 100% rename from api/v1beta1/cluster_cryostat_types.go rename to api/v1beta1/clustercryostat_types.go diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 6f53b135c..13697c8bf 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -4,6 +4,29 @@ metadata: annotations: alm-examples: |- [ + { + "apiVersion": "operator.cryostat.io/v1beta1", + "kind": "ClusterCryostat", + "metadata": { + "name": "clustercryostat-sample" + }, + "spec": { + "enableCertManager": true, + "eventTemplates": [], + "minimal": false, + "reportOptions": { + "replicas": 0 + }, + "storageOptions": { + "pvc": { + "annotations": {}, + "labels": {}, + "spec": {} + } + }, + "trustedCertSecrets": [] + } + }, { "apiVersion": "operator.cryostat.io/v1beta1", "kind": "Cryostat", @@ -58,6 +81,381 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - description: ClusterCryostat allows you to install Cryostat for multiple namespaces + or cluster-wide. It contains configuration options for controlling the Deployment + of the Cryostat application and its related components. A ClusterCryostat + or Cryostat instance must be created to instruct the operator to deploy the + Cryostat application. + displayName: Cluster Cryostat + kind: ClusterCryostat + name: clustercryostats.operator.cryostat.io + resources: + - kind: ConsoleLink + name: "" + version: v1 + - kind: Deployment + name: "" + version: v1 + - kind: Ingress + name: "" + version: v1 + - kind: PersistentVolumeClaim + name: "" + version: v1 + - kind: Route + name: "" + version: v1 + - kind: Secret + name: "" + version: v1 + - kind: Service + name: "" + version: v1 + specDescriptors: + - description: Override default authorization properties for Cryostat on OpenShift. + displayName: Authorization Properties + path: authProperties + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: 'Name of the ClusterRole to use when Cryostat requests a role-scoped + OAuth token. This ClusterRole should contain permissions for all Kubernetes + objects listed in custom permission mapping. More details: https://docs.openshift.com/container-platform/4.11/authentication/tokens-scoping.html#scoping-tokens-role-scope_configuring-internal-oauth' + displayName: ClusterRole Name + path: authProperties.clusterRoleName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ClusterRole + - description: Name of config map in the local namespace. + displayName: ConfigMap Name + path: authProperties.configMapName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap + - description: Filename within config map containing the resource mapping. + displayName: Filename + path: authProperties.filename + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: Use cert-manager to secure in-cluster communication between Cryostat + components. Requires cert-manager to be installed. + displayName: Enable cert-manager Integration + path: enableCertManager + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: List of Flight Recorder Event Templates to preconfigure in Cryostat. + displayName: Event Templates + path: eventTemplates + - description: Name of config map in the local namespace. + displayName: Config Map Name + path: eventTemplates[0].configMapName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap + - description: Options to customize the JMX target connections cache for the + Cryostat application. + displayName: JMX Connections Cache Options + path: jmxCacheOptions + - description: The maximum number of JMX connections to cache. Use `-1` for + an unlimited cache size (TTL expiration only). Defaults to `-1`. + displayName: Target Cache Size + path: jmxCacheOptions.targetCacheSize + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number + - description: The time to live (in seconds) for cached JMX connections. Defaults + to `10`. + displayName: Target Cache TTL + path: jmxCacheOptions.targetCacheTTL + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number + - description: Options to configure the Cryostat application's JMX credentials + database. + displayName: Jmx Credentials Database Options + path: jmxCredentialsDatabaseOptions + - description: Name of the secret containing the password to encrypt JMX credentials + database. + displayName: Database Secret Name + path: jmxCredentialsDatabaseOptions.databaseSecretName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret + - description: The maximum number of WebSocket client connections allowed (minimum + 1, default unlimited). + displayName: Max WebSocket Connections + path: maxWsConnections + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number + - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard + or JFR Data Source. + displayName: Minimal Deployment + path: minimal + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Options to control how the operator exposes the application outside + of the cluster, such as using an Ingress or Route. + displayName: Network Options + path: networkOptions + - description: "Specifications for how to expose the Cryostat command service, + which serves the WebSocket command channel. \n Deprecated: CommandConfig + is no longer used." + displayName: Command Config + path: networkOptions.commandConfig + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden + - description: Annotations to add to the Ingress or Route during its creation. + displayName: Annotations + path: networkOptions.commandConfig.annotations + - description: Configuration for an Ingress object. Currently subpaths are not + supported, so unique hosts must be specified (if a single external IP is + being used) to differentiate between ingresses/services. + displayName: Ingress Spec + path: networkOptions.commandConfig.ingressSpec + - description: Labels to add to the Ingress or Route during its creation. The + label with key "app" is reserved for use by the operator. + displayName: Labels + path: networkOptions.commandConfig.labels + - description: Specifications for how to expose the Cryostat service, which + serves the Cryostat application. + displayName: Core Config + path: networkOptions.coreConfig + - description: Annotations to add to the Ingress or Route during its creation. + displayName: Annotations + path: networkOptions.coreConfig.annotations + - description: Configuration for an Ingress object. Currently subpaths are not + supported, so unique hosts must be specified (if a single external IP is + being used) to differentiate between ingresses/services. + displayName: Ingress Spec + path: networkOptions.coreConfig.ingressSpec + - description: Labels to add to the Ingress or Route during its creation. The + label with key "app" is reserved for use by the operator. + displayName: Labels + path: networkOptions.coreConfig.labels + - description: Specifications for how to expose Cryostat's Grafana service, + which serves the Grafana dashboard. + displayName: Grafana Config + path: networkOptions.grafanaConfig + - description: Annotations to add to the Ingress or Route during its creation. + displayName: Annotations + path: networkOptions.grafanaConfig.annotations + - description: Configuration for an Ingress object. Currently subpaths are not + supported, so unique hosts must be specified (if a single external IP is + being used) to differentiate between ingresses/services. + displayName: Ingress Spec + path: networkOptions.grafanaConfig.ingressSpec + - description: Labels to add to the Ingress or Route during its creation. The + label with key "app" is reserved for use by the operator. + displayName: Labels + path: networkOptions.grafanaConfig.labels + - description: Options to configure Cryostat Automated Report Analysis. + displayName: Report Options + path: reportOptions + - description: The number of report sidecar replica containers to deploy. Each + replica can service one report generation request at a time. + displayName: Replicas + path: reportOptions.replicas + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podCount + - description: The resources allocated to each sidecar replica. A replica with + more resources can handle larger input recordings and will process them + faster. + displayName: Resources + path: reportOptions.resources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Options to configure scheduling for the reports deployment + displayName: Scheduling Options + path: reportOptions.schedulingOptions + - description: Affinity rules for scheduling Cryostat pods. + displayName: Affinity + path: reportOptions.schedulingOptions.affinity + - description: 'Node affinity scheduling rules for a Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' + displayName: Node Affinity + path: reportOptions.schedulingOptions.affinity.nodeAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:nodeAffinity + - description: 'Pod affinity scheduling rules for a Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' + displayName: Pod Affinity + path: reportOptions.schedulingOptions.affinity.podAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAffinity + - description: 'Pod anti-affinity scheduling rules for a Cryostat pod. See: + https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' + displayName: Pod Anti Affinity + path: reportOptions.schedulingOptions.affinity.podAntiAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAntiAffinity + - description: 'Label selector used to schedule a Cryostat pod to a node. See: + https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + displayName: Node Selector + path: reportOptions.schedulingOptions.nodeSelector + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:selector:core:v1:Node + - description: 'Tolerations to allow scheduling of Cryostat pods to tainted + nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + displayName: Tolerations + path: reportOptions.schedulingOptions.tolerations + - description: Options to configure the Security Contexts for the Cryostat report + generator. + displayName: Security Options + path: reportOptions.securityOptions + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: Security Context to apply to the Cryostat report generator pod. + displayName: Pod Security Context + path: reportOptions.securityOptions.podSecurityContext + - description: Security Context to apply to the Cryostat report generator container. + displayName: Reports Security Context + path: reportOptions.securityOptions.reportsSecurityContext + - description: When zero report sidecar replicas are requested, SubProcessMaxHeapSize + configures the maximum heap size of the basic subprocess report generator + in MiB. The default heap size is `200` (MiB). + displayName: Sub Process Max Heap Size + path: reportOptions.subProcessMaxHeapSize + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number + - description: Resource requirements for the Cryostat deployment. + displayName: Resources + path: resources + - description: Resource requirements for the Cryostat application. If specifying + a memory limit, at least 768MiB is recommended. + displayName: Core Resources + path: resources.coreResources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Resource requirements for the JFR Data Source container. + displayName: Data Source Resources + path: resources.dataSourceResources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Resource requirements for the Grafana container. + displayName: Grafana Resources + path: resources.grafanaResources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Options to configure scheduling for the Cryostat deployment + displayName: Scheduling Options + path: schedulingOptions + - description: Affinity rules for scheduling Cryostat pods. + displayName: Affinity + path: schedulingOptions.affinity + - description: 'Node affinity scheduling rules for a Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' + displayName: Node Affinity + path: schedulingOptions.affinity.nodeAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:nodeAffinity + - description: 'Pod affinity scheduling rules for a Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' + displayName: Pod Affinity + path: schedulingOptions.affinity.podAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAffinity + - description: 'Pod anti-affinity scheduling rules for a Cryostat pod. See: + https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' + displayName: Pod Anti Affinity + path: schedulingOptions.affinity.podAntiAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAntiAffinity + - description: 'Label selector used to schedule a Cryostat pod to a node. See: + https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + displayName: Node Selector + path: schedulingOptions.nodeSelector + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:selector:core:v1:Node + - description: 'Tolerations to allow scheduling of Cryostat pods to tainted + nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + displayName: Tolerations + path: schedulingOptions.tolerations + - description: Options to configure the Security Contexts for the Cryostat application. + displayName: Security Options + path: securityOptions + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: Security Context to apply to the Cryostat application container. + displayName: Core Security Context + path: securityOptions.coreSecurityContext + - description: Security Context to apply to the JFR Data Source container. + displayName: Data Source Security Context + path: securityOptions.dataSourceSecurityContext + - description: Security Context to apply to the Grafana container. + displayName: Grafana Security Context + path: securityOptions.grafanaSecurityContext + - description: Security Context to apply to the Cryostat pod. + displayName: Pod Security Context + path: securityOptions.podSecurityContext + - description: Options to customize the services created for the Cryostat application + and Grafana dashboard. + displayName: Service Options + path: serviceOptions + - description: Options to customize the storage for Flight Recordings and Templates. + displayName: Storage Options + path: storageOptions + - description: Configuration for an EmptyDir to be created by the operator instead + of a PVC. + displayName: Empty Dir + path: storageOptions.emptyDir + - description: When enabled, Cryostat will use EmptyDir volumes instead of a + Persistent Volume Claim. Any PVC configurations will be ignored. + displayName: Enabled + path: storageOptions.emptyDir.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Unless specified, the emptyDir volume will be mounted on the + same storage medium backing the node. Setting this field to "Memory" will + mount the emptyDir on a tmpfs (RAM-backed filesystem). + displayName: Medium + path: storageOptions.emptyDir.medium + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldDependency:storageOptions.emptyDir.enabled:true + - description: The maximum memory limit for the emptyDir. Default is unbounded. + displayName: Size Limit + path: storageOptions.emptyDir.sizeLimit + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldDependency:storageOptions.emptyDir.enabled:true + - description: Configuration for the Persistent Volume Claim to be created by + the operator. + displayName: PVC + path: storageOptions.pvc + - description: Spec for a Persistent Volume Claim, whose options will override + the defaults used by the operator. Unless overriden, the PVC will be created + with the default Storage Class and 500MiB of storage. Once the operator + has created the PVC, changes to this field have no effect. + displayName: Spec + path: storageOptions.pvc.spec + - description: Options to configure the Cryostat application's target discovery + mechanisms. + displayName: Target Discovery Options + path: targetDiscoveryOptions + - description: When true, the Cryostat application will disable the built-in + discovery mechanisms. Defaults to false + displayName: Disable Built-in Discovery + path: targetDiscoveryOptions.builtInDiscoveryDisabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: List of TLS certificates to trust when connecting to targets. + displayName: Trusted TLS Certificates + path: trustedCertSecrets + - description: Name of secret in the local namespace. + displayName: Secret Name + path: trustedCertSecrets[0].secretName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret + statusDescriptors: + - description: Address of the deployed Cryostat web application. + displayName: Application URL + path: applicationUrl + x-descriptors: + - urn:alm:descriptor:org.w3:link + - description: Conditions of the components managed by the Cryostat Operator. + displayName: Cryostat Conditions + path: conditions + x-descriptors: + - urn:alm:descriptor:io.kubernetes.conditions + - description: Name of the Secret containing the generated Grafana credentials. + displayName: Grafana Secret + path: grafanaSecret + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret + - description: List of namespaces that Cryostat has been configured and authorized + to access and profile. + displayName: Target Namespaces + path: targetNamespaces + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Namespace + version: v1beta1 - description: Cryostat allows you to install Cryostat for a single namespace. It contains configuration options for controlling the Deployment of the Cryostat application and its related components. A ClusterCryostat or Cryostat instance diff --git a/bundle/manifests/operator.cryostat.io_clustercryostats.yaml b/bundle/manifests/operator.cryostat.io_clustercryostats.yaml new file mode 100644 index 000000000..e1bd96765 --- /dev/null +++ b/bundle/manifests/operator.cryostat.io_clustercryostats.yaml @@ -0,0 +1,4521 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: clustercryostats.operator.cryostat.io +spec: + group: operator.cryostat.io + names: + kind: ClusterCryostat + listKind: ClusterCryostatList + plural: clustercryostats + singular: clustercryostat + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.applicationUrl + name: Application URL + type: string + - jsonPath: .status.grafanaSecret + name: Grafana Secret + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: ClusterCryostat allows you to install Cryostat for multiple namespaces + or cluster-wide. It contains configuration options for controlling the Deployment + of the Cryostat application and its related components. A ClusterCryostat + or Cryostat instance must be created to instruct the operator to deploy + the Cryostat application. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ClusterCryostatSpec defines the desired state of ClusterCryostat. + properties: + authProperties: + description: Override default authorization properties for Cryostat + on OpenShift. + properties: + clusterRoleName: + description: 'Name of the ClusterRole to use when Cryostat requests + a role-scoped OAuth token. This ClusterRole should contain permissions + for all Kubernetes objects listed in custom permission mapping. + More details: https://docs.openshift.com/container-platform/4.11/authentication/tokens-scoping.html#scoping-tokens-role-scope_configuring-internal-oauth' + type: string + configMapName: + description: Name of config map in the local namespace. + type: string + filename: + description: Filename within config map containing the resource + mapping. + type: string + required: + - clusterRoleName + - configMapName + - filename + type: object + enableCertManager: + description: Use cert-manager to secure in-cluster communication between + Cryostat components. Requires cert-manager to be installed. + type: boolean + eventTemplates: + description: List of Flight Recorder Event Templates to preconfigure + in Cryostat. + items: + description: A ConfigMap containing a .jfc template file. + properties: + configMapName: + description: Name of config map in the local namespace. + type: string + filename: + description: Filename within config map containing the template + file. + type: string + required: + - configMapName + - filename + type: object + type: array + installNamespace: + description: Namespace where Cryostat should be installed. + type: string + jmxCacheOptions: + description: Options to customize the JMX target connections cache + for the Cryostat application. + properties: + targetCacheSize: + description: The maximum number of JMX connections to cache. Use + `-1` for an unlimited cache size (TTL expiration only). Defaults + to `-1`. + format: int32 + minimum: -1 + type: integer + targetCacheTTL: + description: The time to live (in seconds) for cached JMX connections. + Defaults to `10`. + format: int32 + minimum: 1 + type: integer + type: object + jmxCredentialsDatabaseOptions: + description: Options to configure the Cryostat application's JMX credentials + database. + properties: + databaseSecretName: + description: Name of the secret containing the password to encrypt + JMX credentials database. + type: string + type: object + maxWsConnections: + description: The maximum number of WebSocket client connections allowed + (minimum 1, default unlimited). + format: int32 + minimum: 1 + type: integer + minimal: + description: Deploy a pared-down Cryostat instance with no Grafana + Dashboard or JFR Data Source. + type: boolean + networkOptions: + description: Options to control how the operator exposes the application + outside of the cluster, such as using an Ingress or Route. + properties: + commandConfig: + description: "Specifications for how to expose the Cryostat command + service, which serves the WebSocket command channel. \n Deprecated: + CommandConfig is no longer used." + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Ingress or Route during + its creation. + type: object + ingressSpec: + description: Configuration for an Ingress object. Currently + subpaths are not supported, so unique hosts must be specified + (if a single external IP is being used) to differentiate + between ingresses/services. + properties: + defaultBackend: + description: DefaultBackend is the backend that should + handle requests that don't match any rule. If Rules + are not specified, DefaultBackend must be specified. + If DefaultBackend is not set, the handling of requests + that do not match any of the rules will be up to the + Ingress controller. + properties: + resource: + description: Resource is an ObjectRef to another Kubernetes + resource in the namespace of the Ingress object. + If resource is specified, a service.Name and service.Port + must not be specified. This is a mutually exclusive + setting with "Service". + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is + required. + type: string + kind: + description: Kind is the type of resource being + referenced + type: string + name: + description: Name is the name of resource being + referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a Service as a Backend. + This is a mutually exclusive setting with "Resource". + properties: + name: + description: Name is the referenced service. The + service must exist in the same namespace as + the Ingress object. + type: string + port: + description: Port of the referenced service. A + port name or port number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name of the port + on the Service. This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the numerical port + number (e.g. 80) on the Service. This is + a mutually exclusive setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + ingressClassName: + description: IngressClassName is the name of the IngressClass + cluster resource. The associated IngressClass defines + which controller will implement the resource. This replaces + the deprecated `kubernetes.io/ingress.class` annotation. + For backwards compatibility, when that annotation is + set, it must be given precedence over this field. The + controller may emit a warning if the field and annotation + have different values. Implementations of this API should + ignore Ingresses without a class specified. An IngressClass + resource may be marked as default, which can be used + to set a default value for this field. For more information, + refer to the IngressClass documentation. + type: string + rules: + description: A list of host rules used to configure the + Ingress. If unspecified, or no rule matches, all traffic + is sent to the default backend. + items: + description: IngressRule represents the rules mapping + the paths under a specified host to the related backend + services. Incoming requests are first evaluated for + a host match, then routed to the backend associated + with the matching IngressRuleValue. + properties: + host: + description: "Host is the fully qualified domain + name of a network host, as defined by RFC 3986. + Note the following deviations from the \"host\" + part of the URI as defined in RFC 3986: 1. IPs + are not allowed. Currently an IngressRuleValue + can only apply to the IP in the Spec of the parent + Ingress. 2. The `:` delimiter is not respected + because ports are not allowed. Currently the port + of an Ingress is implicitly :80 for http and :443 + for https. Both these may change in the future. + Incoming requests are matched against the host + before the IngressRuleValue. If the host is unspecified, + the Ingress routes all traffic based on the specified + IngressRuleValue. \n Host can be \"precise\" which + is a domain name without the terminating dot of + a network host (e.g. \"foo.bar.com\") or \"wildcard\", + which is a domain name prefixed with a single + wildcard label (e.g. \"*.foo.com\"). The wildcard + character '*' must appear by itself as the first + DNS label and matches only a single label. You + cannot have a wildcard label by itself (e.g. Host + == \"*\"). Requests will be matched against the + Host field in the following way: 1. If Host is + precise, the request matches this rule if the + http host header is equal to Host. 2. If Host + is a wildcard, then the request matches this rule + if the http host header is to equal to the suffix + (removing the first label) of the wildcard rule." + type: string + http: + description: 'HTTPIngressRuleValue is a list of + http selectors pointing to backends. In the example: + http:///? -> backend where + where parts of the url correspond to RFC 3986, + this resource will be used to match against everything + after the last ''/'' and before the first ''?'' + or ''#''.' + properties: + paths: + description: A collection of paths that map + requests to backends. + items: + description: HTTPIngressPath associates a + path with a backend. Incoming urls matching + the path are forwarded to the backend. + properties: + backend: + description: Backend defines the referenced + service endpoint to which the traffic + will be forwarded to. + properties: + resource: + description: Resource is an ObjectRef + to another Kubernetes resource in + the namespace of the Ingress object. + If resource is specified, a service.Name + and service.Port must not be specified. + This is a mutually exclusive setting + with "Service". + properties: + apiGroup: + description: APIGroup is the group + for the resource being referenced. + If APIGroup is not specified, + the specified Kind must be in + the core API group. For any + other third-party types, APIGroup + is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a + Service as a Backend. This is a + mutually exclusive setting with + "Resource". + properties: + name: + description: Name is the referenced + service. The service must exist + in the same namespace as the + Ingress object. + type: string + port: + description: Port of the referenced + service. A port name or port + number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name + of the port on the Service. + This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the + numerical port number (e.g. + 80) on the Service. This + is a mutually exclusive + setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + path: + description: Path is matched against the + path of an incoming request. Currently + it can contain characters disallowed + from the conventional "path" part of + a URL as defined by RFC 3986. Paths + must begin with a '/' and must be present + when using PathType with value "Exact" + or "Prefix". + type: string + pathType: + description: 'PathType determines the + interpretation of the Path matching. + PathType can be one of the following + values: * Exact: Matches the URL path + exactly. * Prefix: Matches based on + a URL path prefix split by ''/''. Matching + is done on a path element by element + basis. A path element refers is the + list of labels in the path split by + the ''/'' separator. A request is a + match for path p if every p is an element-wise + prefix of p of the request path. Note + that if the last element of the path + is a substring of the last element in + request path, it is not a match (e.g. + /foo/bar matches /foo/bar/baz, but does + not match /foo/barbaz). * ImplementationSpecific: + Interpretation of the Path matching + is up to the IngressClass. Implementations + can treat this as a separate PathType + or treat it identically to Prefix or + Exact path types. Implementations are + required to support all path types.' + type: string + required: + - backend + - pathType + type: object + type: array + x-kubernetes-list-type: atomic + required: + - paths + type: object + type: object + type: array + x-kubernetes-list-type: atomic + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Ingress or Route during + its creation. The label with key "app" is reserved for use + by the operator. + type: object + type: object + coreConfig: + description: Specifications for how to expose the Cryostat service, + which serves the Cryostat application. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Ingress or Route during + its creation. + type: object + ingressSpec: + description: Configuration for an Ingress object. Currently + subpaths are not supported, so unique hosts must be specified + (if a single external IP is being used) to differentiate + between ingresses/services. + properties: + defaultBackend: + description: DefaultBackend is the backend that should + handle requests that don't match any rule. If Rules + are not specified, DefaultBackend must be specified. + If DefaultBackend is not set, the handling of requests + that do not match any of the rules will be up to the + Ingress controller. + properties: + resource: + description: Resource is an ObjectRef to another Kubernetes + resource in the namespace of the Ingress object. + If resource is specified, a service.Name and service.Port + must not be specified. This is a mutually exclusive + setting with "Service". + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is + required. + type: string + kind: + description: Kind is the type of resource being + referenced + type: string + name: + description: Name is the name of resource being + referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a Service as a Backend. + This is a mutually exclusive setting with "Resource". + properties: + name: + description: Name is the referenced service. The + service must exist in the same namespace as + the Ingress object. + type: string + port: + description: Port of the referenced service. A + port name or port number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name of the port + on the Service. This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the numerical port + number (e.g. 80) on the Service. This is + a mutually exclusive setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + ingressClassName: + description: IngressClassName is the name of the IngressClass + cluster resource. The associated IngressClass defines + which controller will implement the resource. This replaces + the deprecated `kubernetes.io/ingress.class` annotation. + For backwards compatibility, when that annotation is + set, it must be given precedence over this field. The + controller may emit a warning if the field and annotation + have different values. Implementations of this API should + ignore Ingresses without a class specified. An IngressClass + resource may be marked as default, which can be used + to set a default value for this field. For more information, + refer to the IngressClass documentation. + type: string + rules: + description: A list of host rules used to configure the + Ingress. If unspecified, or no rule matches, all traffic + is sent to the default backend. + items: + description: IngressRule represents the rules mapping + the paths under a specified host to the related backend + services. Incoming requests are first evaluated for + a host match, then routed to the backend associated + with the matching IngressRuleValue. + properties: + host: + description: "Host is the fully qualified domain + name of a network host, as defined by RFC 3986. + Note the following deviations from the \"host\" + part of the URI as defined in RFC 3986: 1. IPs + are not allowed. Currently an IngressRuleValue + can only apply to the IP in the Spec of the parent + Ingress. 2. The `:` delimiter is not respected + because ports are not allowed. Currently the port + of an Ingress is implicitly :80 for http and :443 + for https. Both these may change in the future. + Incoming requests are matched against the host + before the IngressRuleValue. If the host is unspecified, + the Ingress routes all traffic based on the specified + IngressRuleValue. \n Host can be \"precise\" which + is a domain name without the terminating dot of + a network host (e.g. \"foo.bar.com\") or \"wildcard\", + which is a domain name prefixed with a single + wildcard label (e.g. \"*.foo.com\"). The wildcard + character '*' must appear by itself as the first + DNS label and matches only a single label. You + cannot have a wildcard label by itself (e.g. Host + == \"*\"). Requests will be matched against the + Host field in the following way: 1. If Host is + precise, the request matches this rule if the + http host header is equal to Host. 2. If Host + is a wildcard, then the request matches this rule + if the http host header is to equal to the suffix + (removing the first label) of the wildcard rule." + type: string + http: + description: 'HTTPIngressRuleValue is a list of + http selectors pointing to backends. In the example: + http:///? -> backend where + where parts of the url correspond to RFC 3986, + this resource will be used to match against everything + after the last ''/'' and before the first ''?'' + or ''#''.' + properties: + paths: + description: A collection of paths that map + requests to backends. + items: + description: HTTPIngressPath associates a + path with a backend. Incoming urls matching + the path are forwarded to the backend. + properties: + backend: + description: Backend defines the referenced + service endpoint to which the traffic + will be forwarded to. + properties: + resource: + description: Resource is an ObjectRef + to another Kubernetes resource in + the namespace of the Ingress object. + If resource is specified, a service.Name + and service.Port must not be specified. + This is a mutually exclusive setting + with "Service". + properties: + apiGroup: + description: APIGroup is the group + for the resource being referenced. + If APIGroup is not specified, + the specified Kind must be in + the core API group. For any + other third-party types, APIGroup + is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a + Service as a Backend. This is a + mutually exclusive setting with + "Resource". + properties: + name: + description: Name is the referenced + service. The service must exist + in the same namespace as the + Ingress object. + type: string + port: + description: Port of the referenced + service. A port name or port + number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name + of the port on the Service. + This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the + numerical port number (e.g. + 80) on the Service. This + is a mutually exclusive + setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + path: + description: Path is matched against the + path of an incoming request. Currently + it can contain characters disallowed + from the conventional "path" part of + a URL as defined by RFC 3986. Paths + must begin with a '/' and must be present + when using PathType with value "Exact" + or "Prefix". + type: string + pathType: + description: 'PathType determines the + interpretation of the Path matching. + PathType can be one of the following + values: * Exact: Matches the URL path + exactly. * Prefix: Matches based on + a URL path prefix split by ''/''. Matching + is done on a path element by element + basis. A path element refers is the + list of labels in the path split by + the ''/'' separator. A request is a + match for path p if every p is an element-wise + prefix of p of the request path. Note + that if the last element of the path + is a substring of the last element in + request path, it is not a match (e.g. + /foo/bar matches /foo/bar/baz, but does + not match /foo/barbaz). * ImplementationSpecific: + Interpretation of the Path matching + is up to the IngressClass. Implementations + can treat this as a separate PathType + or treat it identically to Prefix or + Exact path types. Implementations are + required to support all path types.' + type: string + required: + - backend + - pathType + type: object + type: array + x-kubernetes-list-type: atomic + required: + - paths + type: object + type: object + type: array + x-kubernetes-list-type: atomic + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Ingress or Route during + its creation. The label with key "app" is reserved for use + by the operator. + type: object + type: object + grafanaConfig: + description: Specifications for how to expose Cryostat's Grafana + service, which serves the Grafana dashboard. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Ingress or Route during + its creation. + type: object + ingressSpec: + description: Configuration for an Ingress object. Currently + subpaths are not supported, so unique hosts must be specified + (if a single external IP is being used) to differentiate + between ingresses/services. + properties: + defaultBackend: + description: DefaultBackend is the backend that should + handle requests that don't match any rule. If Rules + are not specified, DefaultBackend must be specified. + If DefaultBackend is not set, the handling of requests + that do not match any of the rules will be up to the + Ingress controller. + properties: + resource: + description: Resource is an ObjectRef to another Kubernetes + resource in the namespace of the Ingress object. + If resource is specified, a service.Name and service.Port + must not be specified. This is a mutually exclusive + setting with "Service". + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is + required. + type: string + kind: + description: Kind is the type of resource being + referenced + type: string + name: + description: Name is the name of resource being + referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a Service as a Backend. + This is a mutually exclusive setting with "Resource". + properties: + name: + description: Name is the referenced service. The + service must exist in the same namespace as + the Ingress object. + type: string + port: + description: Port of the referenced service. A + port name or port number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name of the port + on the Service. This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the numerical port + number (e.g. 80) on the Service. This is + a mutually exclusive setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + ingressClassName: + description: IngressClassName is the name of the IngressClass + cluster resource. The associated IngressClass defines + which controller will implement the resource. This replaces + the deprecated `kubernetes.io/ingress.class` annotation. + For backwards compatibility, when that annotation is + set, it must be given precedence over this field. The + controller may emit a warning if the field and annotation + have different values. Implementations of this API should + ignore Ingresses without a class specified. An IngressClass + resource may be marked as default, which can be used + to set a default value for this field. For more information, + refer to the IngressClass documentation. + type: string + rules: + description: A list of host rules used to configure the + Ingress. If unspecified, or no rule matches, all traffic + is sent to the default backend. + items: + description: IngressRule represents the rules mapping + the paths under a specified host to the related backend + services. Incoming requests are first evaluated for + a host match, then routed to the backend associated + with the matching IngressRuleValue. + properties: + host: + description: "Host is the fully qualified domain + name of a network host, as defined by RFC 3986. + Note the following deviations from the \"host\" + part of the URI as defined in RFC 3986: 1. IPs + are not allowed. Currently an IngressRuleValue + can only apply to the IP in the Spec of the parent + Ingress. 2. The `:` delimiter is not respected + because ports are not allowed. Currently the port + of an Ingress is implicitly :80 for http and :443 + for https. Both these may change in the future. + Incoming requests are matched against the host + before the IngressRuleValue. If the host is unspecified, + the Ingress routes all traffic based on the specified + IngressRuleValue. \n Host can be \"precise\" which + is a domain name without the terminating dot of + a network host (e.g. \"foo.bar.com\") or \"wildcard\", + which is a domain name prefixed with a single + wildcard label (e.g. \"*.foo.com\"). The wildcard + character '*' must appear by itself as the first + DNS label and matches only a single label. You + cannot have a wildcard label by itself (e.g. Host + == \"*\"). Requests will be matched against the + Host field in the following way: 1. If Host is + precise, the request matches this rule if the + http host header is equal to Host. 2. If Host + is a wildcard, then the request matches this rule + if the http host header is to equal to the suffix + (removing the first label) of the wildcard rule." + type: string + http: + description: 'HTTPIngressRuleValue is a list of + http selectors pointing to backends. In the example: + http:///? -> backend where + where parts of the url correspond to RFC 3986, + this resource will be used to match against everything + after the last ''/'' and before the first ''?'' + or ''#''.' + properties: + paths: + description: A collection of paths that map + requests to backends. + items: + description: HTTPIngressPath associates a + path with a backend. Incoming urls matching + the path are forwarded to the backend. + properties: + backend: + description: Backend defines the referenced + service endpoint to which the traffic + will be forwarded to. + properties: + resource: + description: Resource is an ObjectRef + to another Kubernetes resource in + the namespace of the Ingress object. + If resource is specified, a service.Name + and service.Port must not be specified. + This is a mutually exclusive setting + with "Service". + properties: + apiGroup: + description: APIGroup is the group + for the resource being referenced. + If APIGroup is not specified, + the specified Kind must be in + the core API group. For any + other third-party types, APIGroup + is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + service: + description: Service references a + Service as a Backend. This is a + mutually exclusive setting with + "Resource". + properties: + name: + description: Name is the referenced + service. The service must exist + in the same namespace as the + Ingress object. + type: string + port: + description: Port of the referenced + service. A port name or port + number is required for a IngressServiceBackend. + properties: + name: + description: Name is the name + of the port on the Service. + This is a mutually exclusive + setting with "Number". + type: string + number: + description: Number is the + numerical port number (e.g. + 80) on the Service. This + is a mutually exclusive + setting with "Name". + format: int32 + type: integer + type: object + required: + - name + type: object + type: object + path: + description: Path is matched against the + path of an incoming request. Currently + it can contain characters disallowed + from the conventional "path" part of + a URL as defined by RFC 3986. Paths + must begin with a '/' and must be present + when using PathType with value "Exact" + or "Prefix". + type: string + pathType: + description: 'PathType determines the + interpretation of the Path matching. + PathType can be one of the following + values: * Exact: Matches the URL path + exactly. * Prefix: Matches based on + a URL path prefix split by ''/''. Matching + is done on a path element by element + basis. A path element refers is the + list of labels in the path split by + the ''/'' separator. A request is a + match for path p if every p is an element-wise + prefix of p of the request path. Note + that if the last element of the path + is a substring of the last element in + request path, it is not a match (e.g. + /foo/bar matches /foo/bar/baz, but does + not match /foo/barbaz). * ImplementationSpecific: + Interpretation of the Path matching + is up to the IngressClass. Implementations + can treat this as a separate PathType + or treat it identically to Prefix or + Exact path types. Implementations are + required to support all path types.' + type: string + required: + - backend + - pathType + type: object + type: array + x-kubernetes-list-type: atomic + required: + - paths + type: object + type: object + type: array + x-kubernetes-list-type: atomic + tls: + description: TLS configuration. Currently the Ingress + only supports a single TLS port, 443. If multiple members + of this list specify different hosts, they will be multiplexed + on the same port according to the hostname specified + through the SNI TLS extension, if the ingress controller + fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer + security associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included + in the TLS certificate. The values in this list + must match the name/s used in the tlsSecret. Defaults + to the wildcard host setting for the loadbalancer + controller fulfilling this Ingress, if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: SecretName is the name of the secret + used to terminate TLS traffic on port 443. Field + is left optional to allow TLS routing based on + SNI hostname alone. If the SNI host in a listener + conflicts with the "Host" header field used by + an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Ingress or Route during + its creation. The label with key "app" is reserved for use + by the operator. + type: object + type: object + type: object + reportOptions: + description: Options to configure Cryostat Automated Report Analysis. + properties: + replicas: + description: The number of report sidecar replica containers to + deploy. Each replica can service one report generation request + at a time. + format: int32 + type: integer + resources: + description: The resources allocated to each sidecar replica. + A replica with more resources can handle larger input recordings + and will process them faster. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + schedulingOptions: + description: Options to configure scheduling for the reports deployment + properties: + affinity: + description: Affinity rules for scheduling Cryostat pods. + properties: + nodeAffinity: + description: 'Node affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. The + node that is most preferred is the one with the + greatest sum of weights, i.e. for each node that + meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements + of this field and adding "weight" to the sum if + the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term + matches all objects with implicit weight 0 (i.e. + it's a no-op). A null preferred scheduling term + matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching + the corresponding nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified + by this field are not met at scheduling time, the + pod will not be scheduled onto the node. If the + affinity requirements specified by this field cease + to be met at some point during pod execution (e.g. + due to an update), the system may or may not try + to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector + terms. The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them + are ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. + If the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: 'Pod affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. The + node that is most preferred is the one with the + greatest sum of weights, i.e. for each node that + meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements + of this field and adding "weight" to the sum if + the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum + are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of + resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set + of namespaces that the term applies to. + The term is applied to the union of the + namespaces selected by this field and + the ones listed in the namespaces field. + null selector and null or empty namespaces + list means "this pod's namespace". An + empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static + list of namespace names that the term + applies to. The term is applied to the + union of the namespaces listed in this + field and the ones selected by namespaceSelector. + null or empty namespaces list and null + namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose + value of the label with key topologyKey + matches that of any node on which any + of the selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching + the corresponding podAffinityTerm, in the + range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified + by this field are not met at scheduling time, the + pod will not be scheduled onto the node. If the + affinity requirements specified by this field cease + to be met at some point during pod execution (e.g. + due to a pod label update), the system may or may + not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes + corresponding to each podAffinityTerm are intersected, + i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those + matching the labelSelector relative to the given + namespace(s)) that this pod should be co-located + (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node + whose value of the label with key + matches that of any node on which a pod of the + set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: 'Pod anti-affinity scheduling rules for a + Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. The + node that is most preferred is the one with the + greatest sum of weights, i.e. for each node that + meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity + expressions, etc.), compute a sum by iterating through + the elements of this field and adding "weight" to + the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum + are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of + resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set + of namespaces that the term applies to. + The term is applied to the union of the + namespaces selected by this field and + the ones listed in the namespaces field. + null selector and null or empty namespaces + list means "this pod's namespace". An + empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. + This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of + {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static + list of namespace names that the term + applies to. The term is applied to the + union of the namespaces listed in this + field and the ones selected by namespaceSelector. + null or empty namespaces list and null + namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose + value of the label with key topologyKey + matches that of any node on which any + of the selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching + the corresponding podAffinityTerm, in the + range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the + pod will not be scheduled onto the node. If the + anti-affinity requirements specified by this field + cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may + or may not try to eventually evict the pod from + its node. When there are multiple elements, the + lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those + matching the labelSelector relative to the given + namespace(s)) that this pod should be co-located + (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node + whose value of the label with key + matches that of any node on which a pod of the + set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: 'Label selector used to schedule a Cryostat pod + to a node. See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + tolerations: + description: 'Tolerations to allow scheduling of Cryostat + pods to tainted nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, + allowed values are NoSchedule, PreferNoSchedule and + NoExecute. + type: string + key: + description: Key is the taint key that the toleration + applies to. Empty means match all taint keys. If the + key is empty, operator must be Exists; this combination + means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship + to the value. Valid operators are Exists and Equal. + Defaults to Equal. Exists is equivalent to wildcard + for value, so that a pod can tolerate all taints of + a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period + of time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the + taint forever (do not evict). Zero and negative values + will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration + matches to. If the operator is Exists, the value should + be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + securityOptions: + description: Options to configure the Security Contexts for the + Cryostat report generator. + properties: + podSecurityContext: + description: Security Context to apply to the Cryostat report + generator pod. + properties: + fsGroup: + description: "A special supplemental group that applies + to all containers in a pod. Some volume types allow + the Kubelet to change the ownership of that volume to + be owned by the pod: \n 1. The owning GID will be the + FSGroup 2. The setgid bit is set (new files created + in the volume will be owned by FSGroup) 3. The permission + bits are OR'd with rw-rw---- \n If unset, the Kubelet + will not modify the ownership and permissions of any + volume. Note that this field cannot be set when spec.os.name + is windows." + format: int64 + type: integer + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of + changing ownership and permission of the volume before + being exposed inside Pod. This field will only apply + to volume types which support fsGroup based ownership(and + permissions). It will have no effect on ephemeral volume + types such as: secret, configmaps and emptydir. Valid + values are "OnRootMismatch" and "Always". If not specified, + "Always" is used. Note that this field cannot be set + when spec.os.name is windows.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this + field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if it + does. If unset or false, no such validation will be + performed. May also be set in SecurityContext. If set + in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all + containers. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this + field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by the containers + in this pod. Note that this field cannot be set when + spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. The + profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's + configured seccomp profile location. Must only be + set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n Localhost + - a profile defined in a file on the node should + be used. RuntimeDefault - the container runtime + default profile should be used. Unconfined - no + profile should be applied." + type: string + required: + - type + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's + primary GID. If unspecified, no groups will be added + to any container. Note that this field cannot be set + when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls + used for the pod. Pods with unsupported sysctls (by + the container runtime) might fail to launch. Note that + this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be + set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options within a + container's SecurityContext will be used. If set in + both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored by + components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the Pod. + All of a Pod's containers must have the same effective + HostProcess value (it is not allowed to have a mix + of HostProcess containers and non-HostProcess containers). In + addition, if HostProcess is true then HostNetwork + must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + reportsSecurityContext: + description: Security Context to apply to the Cryostat report + generator container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag + will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be + set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. Note that this field + cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. Note that this + field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. Note that this field cannot + be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root + filesystem. Default is false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if it + does. If unset or false, no such validation will be + performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name + is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. + Note that this field cannot be set when spec.os.name + is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. The + profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's + configured seccomp profile location. Must only be + set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n Localhost + - a profile defined in a file on the node should + be used. RuntimeDefault - the container runtime + default profile should be used. Unconfined - no + profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set + when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container + should be run as a 'Host Process' container. This + field is alpha-level and will only be honored by + components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature + flag will result in errors when validating the Pod. + All of a Pod's containers must have the same effective + HostProcess value (it is not allowed to have a mix + of HostProcess containers and non-HostProcess containers). In + addition, if HostProcess is true then HostNetwork + must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + type: object + subProcessMaxHeapSize: + description: When zero report sidecar replicas are requested, + SubProcessMaxHeapSize configures the maximum heap size of the + basic subprocess report generator in MiB. The default heap size + is `200` (MiB). + format: int32 + type: integer + type: object + resources: + description: Resource requirements for the Cryostat deployment. + properties: + coreResources: + description: Resource requirements for the Cryostat application. + If specifying a memory limit, at least 768MiB is recommended. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + dataSourceResources: + description: Resource requirements for the JFR Data Source container. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + grafanaResources: + description: Resource requirements for the Grafana container. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + type: object + schedulingOptions: + description: Options to configure scheduling for the Cryostat deployment + properties: + affinity: + description: Affinity rules for scheduling Cryostat pods. + properties: + nodeAffinity: + description: 'Node affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: 'Pod affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: 'Pod anti-affinity scheduling rules for a Cryostat + pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: 'Label selector used to schedule a Cryostat pod to + a node. See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + tolerations: + description: 'Tolerations to allow scheduling of Cryostat pods + to tainted nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + securityOptions: + description: Options to configure the Security Contexts for the Cryostat + application. + properties: + coreSecurityContext: + description: Security Context to apply to the Cryostat application + container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a + process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag will + be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be set + when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the + container runtime. Note that this field cannot be set when + spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes in + privileged containers are essentially equivalent to root + on the host. Defaults to false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use + for the containers. The default is DefaultProcMount which + uses the container runtime defaults for readonly paths and + masked paths. This requires the ProcMountType feature flag + to be enabled. Note that this field cannot be set when spec.os.name + is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when + spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. Note + that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options from the PodSecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is + linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + dataSourceSecurityContext: + description: Security Context to apply to the JFR Data Source + container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a + process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag will + be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be set + when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the + container runtime. Note that this field cannot be set when + spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes in + privileged containers are essentially equivalent to root + on the host. Defaults to false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use + for the containers. The default is DefaultProcMount which + uses the container runtime defaults for readonly paths and + masked paths. This requires the ProcMountType feature flag + to be enabled. Note that this field cannot be set when spec.os.name + is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when + spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. Note + that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options from the PodSecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is + linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + grafanaSecurityContext: + description: Security Context to apply to the Grafana container. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a + process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag will + be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN Note that this field cannot be set + when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the + container runtime. Note that this field cannot be set when + spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes in + privileged containers are essentially equivalent to root + on the host. Defaults to false. Note that this field cannot + be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use + for the containers. The default is DefaultProcMount which + uses the container runtime defaults for readonly paths and + masked paths. This requires the ProcMountType feature flag + to be enabled. Note that this field cannot be set when spec.os.name + is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when + spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & container + level, the container options override the pod options. Note + that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options from the PodSecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is + linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + podSecurityContext: + description: Security Context to apply to the Cryostat pod. + properties: + fsGroup: + description: "A special supplemental group that applies to + all containers in a pod. Some volume types allow the Kubelet + to change the ownership of that volume to be owned by the + pod: \n 1. The owning GID will be the FSGroup 2. The setgid + bit is set (new files created in the volume will be owned + by FSGroup) 3. The permission bits are OR'd with rw-rw---- + \n If unset, the Kubelet will not modify the ownership and + permissions of any volume. Note that this field cannot be + set when spec.os.name is windows." + format: int64 + type: integer + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of changing + ownership and permission of the volume before being exposed + inside Pod. This field will only apply to volume types which + support fsGroup based ownership(and permissions). It will + have no effect on ephemeral volume types such as: secret, + configmaps and emptydir. Valid values are "OnRootMismatch" + and "Always". If not specified, "Always" is used. Note that + this field cannot be set when spec.os.name is windows.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this field + cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in SecurityContext. If set + in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence for that container. + Note that this field cannot be set when spec.os.name is + windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + SecurityContext. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. Note that this field cannot be set when + spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by the containers + in this pod. Note that this field cannot be set when spec.os.name + is windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's primary + GID. If unspecified, no groups will be added to any container. + Note that this field cannot be set when spec.os.name is + windows. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls used + for the pod. Pods with unsupported sysctls (by the container + runtime) might fail to launch. Note that this field cannot + be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options within a container's + SecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. Note that this field cannot be set when + spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is + alpha-level and will only be honored by components that + enable the WindowsHostProcessContainers feature flag. + Setting this field without the feature flag will result + in errors when validating the Pod. All of a Pod's containers + must have the same effective HostProcess value (it is + not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + type: object + serviceOptions: + description: Options to customize the services created for the Cryostat + application and Grafana dashboard. + properties: + coreConfig: + description: Specification for the service responsible for the + Cryostat application. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the service during its + creation. + type: object + httpPort: + description: HTTP port number for the Cryostat application + service. Defaults to 8181. + format: int32 + type: integer + jmxPort: + description: Remote JMX port number for the Cryostat application + service. Defaults to 9091. + format: int32 + type: integer + labels: + additionalProperties: + type: string + description: Labels to add to the service during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + serviceType: + description: Type of service to create. Defaults to "ClusterIP". + type: string + type: object + grafanaConfig: + description: Specification for the service responsible for the + Cryostat Grafana dashboard. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the service during its + creation. + type: object + httpPort: + description: HTTP port number for the Grafana dashboard service. + Defaults to 3000. + format: int32 + type: integer + labels: + additionalProperties: + type: string + description: Labels to add to the service during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + serviceType: + description: Type of service to create. Defaults to "ClusterIP". + type: string + type: object + reportsConfig: + description: Specification for the service responsible for the + cryostat-reports sidecars. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the service during its + creation. + type: object + httpPort: + description: HTTP port number for the cryostat-reports service. + Defaults to 10000. + format: int32 + type: integer + labels: + additionalProperties: + type: string + description: Labels to add to the service during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + serviceType: + description: Type of service to create. Defaults to "ClusterIP". + type: string + type: object + type: object + storageOptions: + description: Options to customize the storage for Flight Recordings + and Templates. + properties: + emptyDir: + description: Configuration for an EmptyDir to be created by the + operator instead of a PVC. + properties: + enabled: + description: When enabled, Cryostat will use EmptyDir volumes + instead of a Persistent Volume Claim. Any PVC configurations + will be ignored. + type: boolean + medium: + description: Unless specified, the emptyDir volume will be + mounted on the same storage medium backing the node. Setting + this field to "Memory" will mount the emptyDir on a tmpfs + (RAM-backed filesystem). + type: string + sizeLimit: + description: The maximum memory limit for the emptyDir. Default + is unbounded. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + type: string + type: object + pvc: + description: Configuration for the Persistent Volume Claim to + be created by the operator. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the Persistent Volume Claim + during its creation. + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the Persistent Volume Claim + during its creation. The label with key "app" is reserved + for use by the operator. + type: object + spec: + description: Spec for a Persistent Volume Claim, whose options + will override the defaults used by the operator. Unless + overriden, the PVC will be created with the default Storage + Class and 500MiB of storage. Once the operator has created + the PVC, changes to this field have no effect. + properties: + accessModes: + description: 'accessModes contains the desired access + modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'dataSource field can be used to specify + either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) If the provisioner + or an external controller can support the specified + data source, it will create a new volume based on the + contents of the specified data source. If the AnyVolumeDataSource + feature gate is enabled, this field will always have + the same contents as the DataSourceRef field.' + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + dataSourceRef: + description: 'dataSourceRef specifies the object from + which to populate the volume with data, if a non-empty + volume is desired. This may be any local object from + a non-empty API group (non core object) or a PersistentVolumeClaim + object. When this field is specified, volume binding + will only succeed if the type of the specified object + matches some installed volume populator or dynamic provisioner. + This field will replace the functionality of the DataSource + field and as such if both fields are non-empty, they + must have the same value. For backwards compatibility, + both fields (DataSource and DataSourceRef) will be set + to the same value automatically if one of them is empty + and the other is non-empty. There are two important + differences between DataSource and DataSourceRef: * + While DataSource only allows two specific types of objects, + DataSourceRef allows any non-core object, as well as + PersistentVolumeClaim objects. * While DataSource ignores + disallowed values (dropping them), DataSourceRef preserves + all values, and generates an error if a disallowed value + is specified. (Beta) Using this field requires the AnyVolumeDataSource + feature gate to be enabled.' + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'resources represents the minimum resources + the volume should have. If RecoverVolumeExpansionFailure + feature is enabled users are allowed to specify resource + requirements that are lower than previous value but + must still be higher than capacity recorded in the status + field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount + of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + selector: + description: selector is a label query over volumes to + consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values + array must be non-empty. If the operator is + Exists or DoesNotExist, the values array must + be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'storageClassName is the name of the StorageClass + required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is + required by the claim. Value of Filesystem is implied + when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference to the + PersistentVolume backing this claim. + type: string + type: object + type: object + type: object + targetDiscoveryOptions: + description: Options to configure the Cryostat application's target + discovery mechanisms. + properties: + builtInDiscoveryDisabled: + description: When true, the Cryostat application will disable + the built-in discovery mechanisms. Defaults to false + type: boolean + type: object + targetNamespaces: + description: List of namespaces whose workloads Cryostat should be + permitted to access and profile. Defaults to `spec.installNamespace`. + items: + type: string + type: array + trustedCertSecrets: + description: List of TLS certificates to trust when connecting to + targets. + items: + properties: + certificateKey: + description: Key within secret containing the certificate. + type: string + secretName: + description: Name of secret in the local namespace. + type: string + required: + - secretName + type: object + type: array + required: + - installNamespace + - minimal + type: object + status: + description: ClusterCryostatStatus defines the observed state of ClusterCryostat. + properties: + applicationUrl: + description: Address of the deployed Cryostat web application. + type: string + conditions: + description: Conditions of the components managed by the Cryostat + Operator. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a foo's + current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + grafanaSecret: + description: Name of the Secret containing the generated Grafana credentials. + type: string + targetNamespaces: + description: List of namespaces that Cryostat has been configured + and authorized to access and profile. + items: + type: string + type: array + required: + - applicationUrl + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 4654f635f..153a06d7c 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,17 +3,20 @@ # It should be run by config/default resources: - bases/operator.cryostat.io_cryostats.yaml +- bases/operator.cryostat.io_clustercryostats.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD #- patches/webhook_in_cryostats.yaml +#- patches/webhook_in_clustercryostats.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD #- patches/cainjection_in_cryostats.yaml +#- patches/cainjection_in_clustercryostats.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_clustercryostats.yaml b/config/crd/patches/cainjection_in_clustercryostats.yaml new file mode 100644 index 000000000..8a74c61fe --- /dev/null +++ b/config/crd/patches/cainjection_in_clustercryostats.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: clustercryostats.operator.cryostat.io diff --git a/config/crd/patches/webhook_in_clustercryostats.yaml b/config/crd/patches/webhook_in_clustercryostats.yaml new file mode 100644 index 000000000..2c578d24d --- /dev/null +++ b/config/crd/patches/webhook_in_clustercryostats.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clustercryostats.operator.cryostat.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 66d1ecbce..c5e013975 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -46,6 +46,381 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - description: ClusterCryostat allows you to install Cryostat for multiple namespaces + or cluster-wide. It contains configuration options for controlling the Deployment + of the Cryostat application and its related components. A ClusterCryostat + or Cryostat instance must be created to instruct the operator to deploy the + Cryostat application. + displayName: Cluster Cryostat + kind: ClusterCryostat + name: clustercryostats.operator.cryostat.io + resources: + - kind: ConsoleLink + name: "" + version: v1 + - kind: Deployment + name: "" + version: v1 + - kind: Ingress + name: "" + version: v1 + - kind: PersistentVolumeClaim + name: "" + version: v1 + - kind: Route + name: "" + version: v1 + - kind: Secret + name: "" + version: v1 + - kind: Service + name: "" + version: v1 + specDescriptors: + - description: Override default authorization properties for Cryostat on OpenShift. + displayName: Authorization Properties + path: authProperties + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: 'Name of the ClusterRole to use when Cryostat requests a role-scoped + OAuth token. This ClusterRole should contain permissions for all Kubernetes + objects listed in custom permission mapping. More details: https://docs.openshift.com/container-platform/4.11/authentication/tokens-scoping.html#scoping-tokens-role-scope_configuring-internal-oauth' + displayName: ClusterRole Name + path: authProperties.clusterRoleName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ClusterRole + - description: Name of config map in the local namespace. + displayName: ConfigMap Name + path: authProperties.configMapName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap + - description: Filename within config map containing the resource mapping. + displayName: Filename + path: authProperties.filename + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text + - description: Use cert-manager to secure in-cluster communication between Cryostat + components. Requires cert-manager to be installed. + displayName: Enable cert-manager Integration + path: enableCertManager + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: List of Flight Recorder Event Templates to preconfigure in Cryostat. + displayName: Event Templates + path: eventTemplates + - description: Name of config map in the local namespace. + displayName: Config Map Name + path: eventTemplates[0].configMapName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:ConfigMap + - description: Options to customize the JMX target connections cache for the + Cryostat application. + displayName: JMX Connections Cache Options + path: jmxCacheOptions + - description: The maximum number of JMX connections to cache. Use `-1` for + an unlimited cache size (TTL expiration only). Defaults to `-1`. + displayName: Target Cache Size + path: jmxCacheOptions.targetCacheSize + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number + - description: The time to live (in seconds) for cached JMX connections. Defaults + to `10`. + displayName: Target Cache TTL + path: jmxCacheOptions.targetCacheTTL + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number + - description: Options to configure the Cryostat application's JMX credentials + database. + displayName: Jmx Credentials Database Options + path: jmxCredentialsDatabaseOptions + - description: Name of the secret containing the password to encrypt JMX credentials + database. + displayName: Database Secret Name + path: jmxCredentialsDatabaseOptions.databaseSecretName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret + - description: The maximum number of WebSocket client connections allowed (minimum + 1, default unlimited). + displayName: Max WebSocket Connections + path: maxWsConnections + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number + - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard + or JFR Data Source. + displayName: Minimal Deployment + path: minimal + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Options to control how the operator exposes the application outside + of the cluster, such as using an Ingress or Route. + displayName: Network Options + path: networkOptions + - description: "Specifications for how to expose the Cryostat command service, + which serves the WebSocket command channel. \n Deprecated: CommandConfig + is no longer used." + displayName: Command Config + path: networkOptions.commandConfig + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden + - description: Annotations to add to the Ingress or Route during its creation. + displayName: Annotations + path: networkOptions.commandConfig.annotations + - description: Configuration for an Ingress object. Currently subpaths are not + supported, so unique hosts must be specified (if a single external IP is + being used) to differentiate between ingresses/services. + displayName: Ingress Spec + path: networkOptions.commandConfig.ingressSpec + - description: Labels to add to the Ingress or Route during its creation. The + label with key "app" is reserved for use by the operator. + displayName: Labels + path: networkOptions.commandConfig.labels + - description: Specifications for how to expose the Cryostat service, which + serves the Cryostat application. + displayName: Core Config + path: networkOptions.coreConfig + - description: Annotations to add to the Ingress or Route during its creation. + displayName: Annotations + path: networkOptions.coreConfig.annotations + - description: Configuration for an Ingress object. Currently subpaths are not + supported, so unique hosts must be specified (if a single external IP is + being used) to differentiate between ingresses/services. + displayName: Ingress Spec + path: networkOptions.coreConfig.ingressSpec + - description: Labels to add to the Ingress or Route during its creation. The + label with key "app" is reserved for use by the operator. + displayName: Labels + path: networkOptions.coreConfig.labels + - description: Specifications for how to expose Cryostat's Grafana service, + which serves the Grafana dashboard. + displayName: Grafana Config + path: networkOptions.grafanaConfig + - description: Annotations to add to the Ingress or Route during its creation. + displayName: Annotations + path: networkOptions.grafanaConfig.annotations + - description: Configuration for an Ingress object. Currently subpaths are not + supported, so unique hosts must be specified (if a single external IP is + being used) to differentiate between ingresses/services. + displayName: Ingress Spec + path: networkOptions.grafanaConfig.ingressSpec + - description: Labels to add to the Ingress or Route during its creation. The + label with key "app" is reserved for use by the operator. + displayName: Labels + path: networkOptions.grafanaConfig.labels + - description: Options to configure Cryostat Automated Report Analysis. + displayName: Report Options + path: reportOptions + - description: The number of report sidecar replica containers to deploy. Each + replica can service one report generation request at a time. + displayName: Replicas + path: reportOptions.replicas + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podCount + - description: The resources allocated to each sidecar replica. A replica with + more resources can handle larger input recordings and will process them + faster. + displayName: Resources + path: reportOptions.resources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Options to configure scheduling for the reports deployment + displayName: Scheduling Options + path: reportOptions.schedulingOptions + - description: Affinity rules for scheduling Cryostat pods. + displayName: Affinity + path: reportOptions.schedulingOptions.affinity + - description: 'Node affinity scheduling rules for a Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' + displayName: Node Affinity + path: reportOptions.schedulingOptions.affinity.nodeAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:nodeAffinity + - description: 'Pod affinity scheduling rules for a Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' + displayName: Pod Affinity + path: reportOptions.schedulingOptions.affinity.podAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAffinity + - description: 'Pod anti-affinity scheduling rules for a Cryostat pod. See: + https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' + displayName: Pod Anti Affinity + path: reportOptions.schedulingOptions.affinity.podAntiAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAntiAffinity + - description: 'Label selector used to schedule a Cryostat pod to a node. See: + https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + displayName: Node Selector + path: reportOptions.schedulingOptions.nodeSelector + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:selector:core:v1:Node + - description: 'Tolerations to allow scheduling of Cryostat pods to tainted + nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + displayName: Tolerations + path: reportOptions.schedulingOptions.tolerations + - description: Options to configure the Security Contexts for the Cryostat report + generator. + displayName: Security Options + path: reportOptions.securityOptions + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: Security Context to apply to the Cryostat report generator pod. + displayName: Pod Security Context + path: reportOptions.securityOptions.podSecurityContext + - description: Security Context to apply to the Cryostat report generator container. + displayName: Reports Security Context + path: reportOptions.securityOptions.reportsSecurityContext + - description: When zero report sidecar replicas are requested, SubProcessMaxHeapSize + configures the maximum heap size of the basic subprocess report generator + in MiB. The default heap size is `200` (MiB). + displayName: Sub Process Max Heap Size + path: reportOptions.subProcessMaxHeapSize + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number + - description: Resource requirements for the Cryostat deployment. + displayName: Resources + path: resources + - description: Resource requirements for the Cryostat application. If specifying + a memory limit, at least 768MiB is recommended. + displayName: Core Resources + path: resources.coreResources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Resource requirements for the JFR Data Source container. + displayName: Data Source Resources + path: resources.dataSourceResources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Resource requirements for the Grafana container. + displayName: Grafana Resources + path: resources.grafanaResources + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:resourceRequirements + - description: Options to configure scheduling for the Cryostat deployment + displayName: Scheduling Options + path: schedulingOptions + - description: Affinity rules for scheduling Cryostat pods. + displayName: Affinity + path: schedulingOptions.affinity + - description: 'Node affinity scheduling rules for a Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#NodeAffinity' + displayName: Node Affinity + path: schedulingOptions.affinity.nodeAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:nodeAffinity + - description: 'Pod affinity scheduling rules for a Cryostat pod. See: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAffinity' + displayName: Pod Affinity + path: schedulingOptions.affinity.podAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAffinity + - description: 'Pod anti-affinity scheduling rules for a Cryostat pod. See: + https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodAntiAffinity' + displayName: Pod Anti Affinity + path: schedulingOptions.affinity.podAntiAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAntiAffinity + - description: 'Label selector used to schedule a Cryostat pod to a node. See: + https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + displayName: Node Selector + path: schedulingOptions.nodeSelector + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:selector:core:v1:Node + - description: 'Tolerations to allow scheduling of Cryostat pods to tainted + nodes. See: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/' + displayName: Tolerations + path: schedulingOptions.tolerations + - description: Options to configure the Security Contexts for the Cryostat application. + displayName: Security Options + path: securityOptions + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:advanced + - description: Security Context to apply to the Cryostat application container. + displayName: Core Security Context + path: securityOptions.coreSecurityContext + - description: Security Context to apply to the JFR Data Source container. + displayName: Data Source Security Context + path: securityOptions.dataSourceSecurityContext + - description: Security Context to apply to the Grafana container. + displayName: Grafana Security Context + path: securityOptions.grafanaSecurityContext + - description: Security Context to apply to the Cryostat pod. + displayName: Pod Security Context + path: securityOptions.podSecurityContext + - description: Options to customize the services created for the Cryostat application + and Grafana dashboard. + displayName: Service Options + path: serviceOptions + - description: Options to customize the storage for Flight Recordings and Templates. + displayName: Storage Options + path: storageOptions + - description: Configuration for an EmptyDir to be created by the operator instead + of a PVC. + displayName: Empty Dir + path: storageOptions.emptyDir + - description: When enabled, Cryostat will use EmptyDir volumes instead of a + Persistent Volume Claim. Any PVC configurations will be ignored. + displayName: Enabled + path: storageOptions.emptyDir.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Unless specified, the emptyDir volume will be mounted on the + same storage medium backing the node. Setting this field to "Memory" will + mount the emptyDir on a tmpfs (RAM-backed filesystem). + displayName: Medium + path: storageOptions.emptyDir.medium + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldDependency:storageOptions.emptyDir.enabled:true + - description: The maximum memory limit for the emptyDir. Default is unbounded. + displayName: Size Limit + path: storageOptions.emptyDir.sizeLimit + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:fieldDependency:storageOptions.emptyDir.enabled:true + - description: Configuration for the Persistent Volume Claim to be created by + the operator. + displayName: PVC + path: storageOptions.pvc + - description: Spec for a Persistent Volume Claim, whose options will override + the defaults used by the operator. Unless overriden, the PVC will be created + with the default Storage Class and 500MiB of storage. Once the operator + has created the PVC, changes to this field have no effect. + displayName: Spec + path: storageOptions.pvc.spec + - description: Options to configure the Cryostat application's target discovery + mechanisms. + displayName: Target Discovery Options + path: targetDiscoveryOptions + - description: When true, the Cryostat application will disable the built-in + discovery mechanisms. Defaults to false + displayName: Disable Built-in Discovery + path: targetDiscoveryOptions.builtInDiscoveryDisabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: List of TLS certificates to trust when connecting to targets. + displayName: Trusted TLS Certificates + path: trustedCertSecrets + - description: Name of secret in the local namespace. + displayName: Secret Name + path: trustedCertSecrets[0].secretName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret + statusDescriptors: + - description: Address of the deployed Cryostat web application. + displayName: Application URL + path: applicationUrl + x-descriptors: + - urn:alm:descriptor:org.w3:link + - description: Conditions of the components managed by the Cryostat Operator. + displayName: Cryostat Conditions + path: conditions + x-descriptors: + - urn:alm:descriptor:io.kubernetes.conditions + - description: Name of the Secret containing the generated Grafana credentials. + displayName: Grafana Secret + path: grafanaSecret + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret + - description: List of namespaces that Cryostat has been configured and authorized + to access and profile. + displayName: Target Namespaces + path: targetNamespaces + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Namespace + version: v1beta1 - description: Cryostat allows you to install Cryostat for a single namespace. It contains configuration options for controlling the Deployment of the Cryostat application and its related components. A ClusterCryostat or Cryostat instance diff --git a/config/rbac/clustercryostat_editor_role.yaml b/config/rbac/clustercryostat_editor_role.yaml new file mode 100644 index 000000000..f095d6ca5 --- /dev/null +++ b/config/rbac/clustercryostat_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit clustercryostats. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: clustercryostat-editor-role +rules: +- apiGroups: + - operator.cryostat.io + resources: + - clustercryostats + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.cryostat.io + resources: + - clustercryostats/status + verbs: + - get diff --git a/config/rbac/clustercryostat_viewer_role.yaml b/config/rbac/clustercryostat_viewer_role.yaml new file mode 100644 index 000000000..fa35fed19 --- /dev/null +++ b/config/rbac/clustercryostat_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view clustercryostats. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: clustercryostat-viewer-role +rules: +- apiGroups: + - operator.cryostat.io + resources: + - clustercryostats + verbs: + - get + - list + - watch +- apiGroups: + - operator.cryostat.io + resources: + - clustercryostats/status + verbs: + - get diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index a92a08cec..3e9e735a7 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,4 +1,5 @@ ## Append samples you want in your CSV to this file as resources ## resources: - operator_v1beta1_cryostat.yaml +- operator_v1beta1_clustercryostat.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/operator_v1beta1_clustercryostat.yaml b/config/samples/operator_v1beta1_clustercryostat.yaml new file mode 100644 index 000000000..d2dcfd741 --- /dev/null +++ b/config/samples/operator_v1beta1_clustercryostat.yaml @@ -0,0 +1,16 @@ +apiVersion: operator.cryostat.io/v1beta1 +kind: ClusterCryostat +metadata: + name: clustercryostat-sample +spec: + minimal: false + enableCertManager: true + trustedCertSecrets: [] + eventTemplates: [] + storageOptions: + pvc: + labels: {} + annotations: {} + spec: {} + reportOptions: + replicas: 0 diff --git a/internal/controllers/cluster_cryostat_controller.go b/internal/controllers/clustercryostat_controller.go similarity index 100% rename from internal/controllers/cluster_cryostat_controller.go rename to internal/controllers/clustercryostat_controller.go diff --git a/internal/controllers/cluster_cryostat_controller_test.go b/internal/controllers/clustercryostat_controller_test.go similarity index 100% rename from internal/controllers/cluster_cryostat_controller_test.go rename to internal/controllers/clustercryostat_controller_test.go diff --git a/internal/controllers/common/resource_definitions/certificates.go b/internal/controllers/common/resource_definitions/certificates.go index bff758799..d3eb0b409 100644 --- a/internal/controllers/common/resource_definitions/certificates.go +++ b/internal/controllers/common/resource_definitions/certificates.go @@ -39,7 +39,7 @@ package resource_definitions import ( "fmt" - "github.com/cryostatio/cryostat-operator/internal/constants" + "github.com/cryostatio/cryostat-operator/internal/controllers/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/model" certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" certMeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 6e9c832f9..084b5ee32 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -44,7 +44,7 @@ import ( "strings" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - "github.com/cryostatio/cryostat-operator/internal/constants" + "github.com/cryostatio/cryostat-operator/internal/controllers/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/model" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" diff --git a/internal/constants/constants.go b/internal/controllers/constants/constants.go similarity index 100% rename from internal/constants/constants.go rename to internal/controllers/constants/constants.go diff --git a/internal/controllers/routes.go b/internal/controllers/routes.go index a11abb1d6..f2136778f 100644 --- a/internal/controllers/routes.go +++ b/internal/controllers/routes.go @@ -43,8 +43,8 @@ import ( "net/url" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - "github.com/cryostatio/cryostat-operator/internal/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" + "github.com/cryostatio/cryostat-operator/internal/controllers/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/model" routev1 "github.com/openshift/api/route/v1" corev1 "k8s.io/api/core/v1" diff --git a/internal/controllers/services.go b/internal/controllers/services.go index bc0b7eabc..3bf6a0edc 100644 --- a/internal/controllers/services.go +++ b/internal/controllers/services.go @@ -43,8 +43,8 @@ import ( "strconv" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - "github.com/cryostatio/cryostat-operator/internal/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" + "github.com/cryostatio/cryostat-operator/internal/controllers/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/model" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" diff --git a/internal/main.go b/internal/main.go index 1ae75c5b4..f6e7e9054 100644 --- a/internal/main.go +++ b/internal/main.go @@ -61,7 +61,6 @@ import ( operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "github.com/cryostatio/cryostat-operator/internal/controllers" "github.com/cryostatio/cryostat-operator/internal/controllers/common" - openshiftv1 "github.com/openshift/api/route/v1" // +kubebuilder:scaffold:imports ) @@ -138,7 +137,7 @@ func main() { certManager := isCertManagerInstalled(apiResources) - if err = (controllers.NewCryostatReconciler(&controllers.ReconcilerConfig{ + config := &controllers.ReconcilerConfig{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("Cryostat"), Scheme: mgr.GetScheme(), @@ -149,7 +148,12 @@ func main() { ReconcilerTLS: common.NewReconcilerTLS(&common.ReconcilerTLSConfig{ Client: mgr.GetClient(), }), - })).SetupWithManager(mgr); err != nil { + } + if err = (controllers.NewClusterCryostatReconciler(config)).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ClusterCryostat") + os.Exit(1) + } + if err = (controllers.NewCryostatReconciler(config)).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Cryostat") os.Exit(1) } @@ -186,7 +190,7 @@ func getWatchNamespace() (string, error) { } func isOpenShift(apiResources []*metav1.APIResourceList) bool { - return lookupGVK(apiResources, openshiftv1.GroupVersion.String(), "Route") + return lookupGVK(apiResources, routev1.GroupVersion.String(), "Route") } func isCertManagerInstalled(apiResources []*metav1.APIResourceList) bool { From 1401673425578e69e5ad09a11880e1d369244900 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Thu, 26 Jan 2023 17:30:46 -0500 Subject: [PATCH 09/21] Use ClusterCryostat as initialization-resource --- ...ryostat-operator.clusterserviceversion.yaml | 4 ++-- ...ryostat-operator.clusterserviceversion.yaml | 18 ++---------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 13697c8bf..b3003cb86 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -59,9 +59,9 @@ metadata: operatorframework.io/initialization-resource: |- { "apiVersion": "operator.cryostat.io/v1beta1", - "kind": "Cryostat", + "kind": "ClusterCryostat", "metadata": { - "name": "cryostat-sample" + "name": "clustercryostat-sample" }, "spec": { "enableCertManager": true, diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index c5e013975..43c4e3fd8 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -2,20 +2,6 @@ apiVersion: operators.coreos.com/v1alpha1 kind: ClusterServiceVersion metadata: annotations: - alm-examples: |- - [ - { - "apiVersion": "operator.cryostat.io/v1beta1", - "kind": "Cryostat", - "metadata": { - "name": "cryostat" - }, - "spec": { - "minimal": false, - "trustedCertSecrets": [] - } - } - ] capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.3.0-dev @@ -24,9 +10,9 @@ metadata: operatorframework.io/initialization-resource: |- { "apiVersion": "operator.cryostat.io/v1beta1", - "kind": "Cryostat", + "kind": "ClusterCryostat", "metadata": { - "name": "cryostat-sample" + "name": "clustercryostat-sample" }, "spec": { "enableCertManager": true, From 998eb03c90557fcea7cb1c805ed5eaa5a02565d3 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Fri, 27 Jan 2023 17:25:47 -0500 Subject: [PATCH 10/21] Use Kustomize patch to adjust x-descriptors --- api/v1beta1/clustercryostat_types.go | 14 ++-- api/v1beta1/cryostat_types.go | 8 +- ...yostat-operator.clusterserviceversion.yaml | 81 +++++++++++-------- ...yostat-operator.clusterserviceversion.yaml | 79 ++++++++++-------- config/manifests/kustomization.yaml | 8 +- config/manifests/targetNamespaces_patch.yaml | 6 ++ 6 files changed, 116 insertions(+), 80 deletions(-) create mode 100644 config/manifests/targetNamespaces_patch.yaml diff --git a/api/v1beta1/clustercryostat_types.go b/api/v1beta1/clustercryostat_types.go index 50656f975..ba7205472 100644 --- a/api/v1beta1/clustercryostat_types.go +++ b/api/v1beta1/clustercryostat_types.go @@ -43,22 +43,24 @@ import ( // ClusterCryostatSpec defines the desired state of ClusterCryostat. type ClusterCryostatSpec struct { // Namespace where Cryostat should be installed. - // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} - InstallNamespace string `json:"installNamespace"` + // +operator-sdk:csv:customresourcedefinitions:type=spec,order=1,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} + InstallNamespace string `json:"installNamespace"` // TODO use order to bring to top // List of namespaces whose workloads Cryostat should be // permitted to access and profile. Defaults to `spec.installNamespace`. - // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} + // +operator-sdk:csv:customresourcedefinitions:type=spec,order=2,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} TargetNamespaces []string `json:"targetNamespaces,omitempty"` - CryostatSpec `json:",inline"` + // +operator-sdk:csv:customresourcedefinitions:type=spec + CryostatSpec `json:",inline"` } // ClusterCryostatStatus defines the observed state of ClusterCryostat. type ClusterCryostatStatus struct { // List of namespaces that Cryostat has been configured // and authorized to access and profile. - // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} + // +operator-sdk:csv:customresourcedefinitions:type=status,order=3,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} TargetNamespaces []string `json:"targetNamespaces,omitempty"` - CryostatStatus `json:",inline"` + // +operator-sdk:csv:customresourcedefinitions:type=status + CryostatStatus `json:",inline"` } // +kubebuilder:object:root=true diff --git a/api/v1beta1/cryostat_types.go b/api/v1beta1/cryostat_types.go index 93fe57db5..f862ff0eb 100644 --- a/api/v1beta1/cryostat_types.go +++ b/api/v1beta1/cryostat_types.go @@ -45,7 +45,7 @@ import ( // CryostatSpec defines the desired state of Cryostat. type CryostatSpec struct { // Deploy a pared-down Cryostat instance with no Grafana Dashboard or JFR Data Source. - // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Minimal Deployment",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + // +operator-sdk:csv:customresourcedefinitions:type=spec,order=4,displayName="Minimal Deployment",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} Minimal bool `json:"minimal"` // List of TLS certificates to trust when connecting to targets. // +optional @@ -58,7 +58,7 @@ type CryostatSpec struct { // Use cert-manager to secure in-cluster communication between Cryostat components. // Requires cert-manager to be installed. // +optional - // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Enable cert-manager Integration",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + // +operator-sdk:csv:customresourcedefinitions:type=spec,order=3,displayName="Enable cert-manager Integration",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} EnableCertManager *bool `json:"enableCertManager"` // Options to customize the storage for Flight Recordings and Templates. // +optional @@ -135,10 +135,10 @@ type CryostatStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` // Name of the Secret containing the generated Grafana credentials. // +optional - // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes:Secret"} + // +operator-sdk:csv:customresourcedefinitions:type=status,order=2,xDescriptors={"urn:alm:descriptor:io.kubernetes:Secret"} GrafanaSecret string `json:"grafanaSecret,omitempty"` // Address of the deployed Cryostat web application. - // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:org.w3:link"} + // +operator-sdk:csv:customresourcedefinitions:type=status,order=1,xDescriptors={"urn:alm:descriptor:org.w3:link"} ApplicationURL string `json:"applicationUrl"` } diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index b3003cb86..f14c5adeb 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -112,6 +112,29 @@ spec: name: "" version: v1 specDescriptors: + - description: Namespace where Cryostat should be installed. + displayName: Install Namespace + path: installNamespace + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Namespace + - description: List of namespaces whose workloads Cryostat should be permitted + to access and profile. Defaults to `spec.installNamespace`. + displayName: Target Namespaces + path: targetNamespaces[0] + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Namespace + - description: Use cert-manager to secure in-cluster communication between Cryostat + components. Requires cert-manager to be installed. + displayName: Enable cert-manager Integration + path: enableCertManager + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard + or JFR Data Source. + displayName: Minimal Deployment + path: minimal + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Override default authorization properties for Cryostat on OpenShift. displayName: Authorization Properties path: authProperties @@ -134,12 +157,6 @@ spec: path: authProperties.filename x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text - - description: Use cert-manager to secure in-cluster communication between Cryostat - components. Requires cert-manager to be installed. - displayName: Enable cert-manager Integration - path: enableCertManager - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: List of Flight Recorder Event Templates to preconfigure in Cryostat. displayName: Event Templates path: eventTemplates @@ -180,12 +197,6 @@ spec: path: maxWsConnections x-descriptors: - urn:alm:descriptor:com.tectonic.ui:number - - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard - or JFR Data Source. - displayName: Minimal Deployment - path: minimal - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. displayName: Network Options @@ -439,11 +450,6 @@ spec: path: applicationUrl x-descriptors: - urn:alm:descriptor:org.w3:link - - description: Conditions of the components managed by the Cryostat Operator. - displayName: Cryostat Conditions - path: conditions - x-descriptors: - - urn:alm:descriptor:io.kubernetes.conditions - description: Name of the Secret containing the generated Grafana credentials. displayName: Grafana Secret path: grafanaSecret @@ -452,9 +458,14 @@ spec: - description: List of namespaces that Cryostat has been configured and authorized to access and profile. displayName: Target Namespaces - path: targetNamespaces + path: targetNamespaces[0] x-descriptors: - urn:alm:descriptor:io.kubernetes:Namespace + - description: Conditions of the components managed by the Cryostat Operator. + displayName: Cryostat Conditions + path: conditions + x-descriptors: + - urn:alm:descriptor:io.kubernetes.conditions version: v1beta1 - description: Cryostat allows you to install Cryostat for a single namespace. It contains configuration options for controlling the Deployment of the Cryostat @@ -486,6 +497,18 @@ spec: name: "" version: v1 specDescriptors: + - description: Use cert-manager to secure in-cluster communication between Cryostat + components. Requires cert-manager to be installed. + displayName: Enable cert-manager Integration + path: enableCertManager + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard + or JFR Data Source. + displayName: Minimal Deployment + path: minimal + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Override default authorization properties for Cryostat on OpenShift. displayName: Authorization Properties path: authProperties @@ -508,12 +531,6 @@ spec: path: authProperties.filename x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text - - description: Use cert-manager to secure in-cluster communication between Cryostat - components. Requires cert-manager to be installed. - displayName: Enable cert-manager Integration - path: enableCertManager - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: List of Flight Recorder Event Templates to preconfigure in Cryostat. displayName: Event Templates path: eventTemplates @@ -554,12 +571,6 @@ spec: path: maxWsConnections x-descriptors: - urn:alm:descriptor:com.tectonic.ui:number - - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard - or JFR Data Source. - displayName: Minimal Deployment - path: minimal - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. displayName: Network Options @@ -813,16 +824,16 @@ spec: path: applicationUrl x-descriptors: - urn:alm:descriptor:org.w3:link - - description: Conditions of the components managed by the Cryostat Operator. - displayName: Cryostat Conditions - path: conditions - x-descriptors: - - urn:alm:descriptor:io.kubernetes.conditions - description: Name of the Secret containing the generated Grafana credentials. displayName: Grafana Secret path: grafanaSecret x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret + - description: Conditions of the components managed by the Cryostat Operator. + displayName: Cryostat Conditions + path: conditions + x-descriptors: + - urn:alm:descriptor:io.kubernetes.conditions version: v1beta1 description: | Cryostat provides a cloud-based solution for interacting with the JDK Flight Recorder already present in OpenJDK 11+ JVMs. With Cryostat, users can remotely start, stop, retrieve, and even analyze JFR event data, providing the capability to easily take advantage of Flight Recorder's extremely low runtime cost and overhead and the flexibility to monitor applications and analyze recording data without transferring data outside of the cluster the application runs within. diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 43c4e3fd8..22788a71c 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -63,6 +63,29 @@ spec: name: "" version: v1 specDescriptors: + - description: Namespace where Cryostat should be installed. + displayName: Install Namespace + path: installNamespace + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Namespace + - description: List of namespaces whose workloads Cryostat should be permitted + to access and profile. Defaults to `spec.installNamespace`. + displayName: Target Namespaces + path: targetNamespaces + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Namespace + - description: Use cert-manager to secure in-cluster communication between Cryostat + components. Requires cert-manager to be installed. + displayName: Enable cert-manager Integration + path: enableCertManager + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard + or JFR Data Source. + displayName: Minimal Deployment + path: minimal + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Override default authorization properties for Cryostat on OpenShift. displayName: Authorization Properties path: authProperties @@ -85,12 +108,6 @@ spec: path: authProperties.filename x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text - - description: Use cert-manager to secure in-cluster communication between Cryostat - components. Requires cert-manager to be installed. - displayName: Enable cert-manager Integration - path: enableCertManager - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: List of Flight Recorder Event Templates to preconfigure in Cryostat. displayName: Event Templates path: eventTemplates @@ -131,12 +148,6 @@ spec: path: maxWsConnections x-descriptors: - urn:alm:descriptor:com.tectonic.ui:number - - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard - or JFR Data Source. - displayName: Minimal Deployment - path: minimal - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. displayName: Network Options @@ -390,11 +401,6 @@ spec: path: applicationUrl x-descriptors: - urn:alm:descriptor:org.w3:link - - description: Conditions of the components managed by the Cryostat Operator. - displayName: Cryostat Conditions - path: conditions - x-descriptors: - - urn:alm:descriptor:io.kubernetes.conditions - description: Name of the Secret containing the generated Grafana credentials. displayName: Grafana Secret path: grafanaSecret @@ -406,6 +412,11 @@ spec: path: targetNamespaces x-descriptors: - urn:alm:descriptor:io.kubernetes:Namespace + - description: Conditions of the components managed by the Cryostat Operator. + displayName: Cryostat Conditions + path: conditions + x-descriptors: + - urn:alm:descriptor:io.kubernetes.conditions version: v1beta1 - description: Cryostat allows you to install Cryostat for a single namespace. It contains configuration options for controlling the Deployment of the Cryostat @@ -437,6 +448,18 @@ spec: name: "" version: v1 specDescriptors: + - description: Use cert-manager to secure in-cluster communication between Cryostat + components. Requires cert-manager to be installed. + displayName: Enable cert-manager Integration + path: enableCertManager + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard + or JFR Data Source. + displayName: Minimal Deployment + path: minimal + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Override default authorization properties for Cryostat on OpenShift. displayName: Authorization Properties path: authProperties @@ -459,12 +482,6 @@ spec: path: authProperties.filename x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text - - description: Use cert-manager to secure in-cluster communication between Cryostat - components. Requires cert-manager to be installed. - displayName: Enable cert-manager Integration - path: enableCertManager - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: List of Flight Recorder Event Templates to preconfigure in Cryostat. displayName: Event Templates path: eventTemplates @@ -505,12 +522,6 @@ spec: path: maxWsConnections x-descriptors: - urn:alm:descriptor:com.tectonic.ui:number - - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard - or JFR Data Source. - displayName: Minimal Deployment - path: minimal - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. displayName: Network Options @@ -764,16 +775,16 @@ spec: path: applicationUrl x-descriptors: - urn:alm:descriptor:org.w3:link - - description: Conditions of the components managed by the Cryostat Operator. - displayName: Cryostat Conditions - path: conditions - x-descriptors: - - urn:alm:descriptor:io.kubernetes.conditions - description: Name of the Secret containing the generated Grafana credentials. displayName: Grafana Secret path: grafanaSecret x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret + - description: Conditions of the components managed by the Cryostat Operator. + displayName: Cryostat Conditions + path: conditions + x-descriptors: + - urn:alm:descriptor:io.kubernetes.conditions version: v1beta1 description: | Cryostat provides a cloud-based solution for interacting with the JDK Flight Recorder already present in OpenJDK 11+ JVMs. With Cryostat, users can remotely start, stop, retrieve, and even analyze JFR event data, providing the capability to easily take advantage of Flight Recorder's extremely low runtime cost and overhead and the flexibility to monitor applications and analyze recording data without transferring data outside of the cluster the application runs within. diff --git a/config/manifests/kustomization.yaml b/config/manifests/kustomization.yaml index c5729bac0..7505e1941 100644 --- a/config/manifests/kustomization.yaml +++ b/config/manifests/kustomization.yaml @@ -4,7 +4,13 @@ resources: - ../samples - ../scorecard -#patchesJson6902: +patchesJson6902: +- path: targetNamespaces_patch.yaml + target: + group: operators.coreos.com + version: v1alpha1 + kind: ClusterServiceVersion + name: cryostat-operator.v0.0.0 #- target: # group: apps # version: v1 diff --git a/config/manifests/targetNamespaces_patch.yaml b/config/manifests/targetNamespaces_patch.yaml new file mode 100644 index 000000000..9e7a73754 --- /dev/null +++ b/config/manifests/targetNamespaces_patch.yaml @@ -0,0 +1,6 @@ +- op: replace + path: /spec/customresourcedefinitions/owned/0/specDescriptors/1/path + value: targetNamespaces[0] +- op: replace + path: /spec/customresourcedefinitions/owned/0/statusDescriptors/2/path + value: targetNamespaces[0] From 1f5c9ffeda25f327658699a0c2611c4a6630e206 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Wed, 1 Feb 2023 16:39:48 -0500 Subject: [PATCH 11/21] Use Kustomize patch to add x-descriptors to targetNamespace elements --- api/v1beta1/clustercryostat_types.go | 6 ++--- ...yostat-operator.clusterserviceversion.yaml | 20 ++++++++++++----- ...yostat-operator.clusterserviceversion.yaml | 4 ---- config/manifests/targetNamespaces_patch.yaml | 22 ++++++++++++++----- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/api/v1beta1/clustercryostat_types.go b/api/v1beta1/clustercryostat_types.go index ba7205472..4f6c79468 100644 --- a/api/v1beta1/clustercryostat_types.go +++ b/api/v1beta1/clustercryostat_types.go @@ -47,17 +47,17 @@ type ClusterCryostatSpec struct { InstallNamespace string `json:"installNamespace"` // TODO use order to bring to top // List of namespaces whose workloads Cryostat should be // permitted to access and profile. Defaults to `spec.installNamespace`. - // +operator-sdk:csv:customresourcedefinitions:type=spec,order=2,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} + // +operator-sdk:csv:customresourcedefinitions:type=spec,order=2 TargetNamespaces []string `json:"targetNamespaces,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec CryostatSpec `json:",inline"` } // ClusterCryostatStatus defines the observed state of ClusterCryostat. -type ClusterCryostatStatus struct { +type ClusterCryostatStatus struct { // FIXME only conditions are showing in console // List of namespaces that Cryostat has been configured // and authorized to access and profile. - // +operator-sdk:csv:customresourcedefinitions:type=status,order=3,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} + // +operator-sdk:csv:customresourcedefinitions:type=status,order=3 TargetNamespaces []string `json:"targetNamespaces,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=status CryostatStatus `json:",inline"` diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index f14c5adeb..6462abf0f 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -120,9 +120,7 @@ spec: - description: List of namespaces whose workloads Cryostat should be permitted to access and profile. Defaults to `spec.installNamespace`. displayName: Target Namespaces - path: targetNamespaces[0] - x-descriptors: - - urn:alm:descriptor:io.kubernetes:Namespace + path: targetNamespaces - description: Use cert-manager to secure in-cluster communication between Cryostat components. Requires cert-manager to be installed. displayName: Enable cert-manager Integration @@ -444,6 +442,12 @@ spec: path: trustedCertSecrets[0].secretName x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret + - description: A namespace whose workloads Cryostat should be able to connect + and record + displayName: Target Namespace + path: targetNamespaces[0] + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Namespace statusDescriptors: - description: Address of the deployed Cryostat web application. displayName: Application URL @@ -458,14 +462,18 @@ spec: - description: List of namespaces that Cryostat has been configured and authorized to access and profile. displayName: Target Namespaces - path: targetNamespaces[0] - x-descriptors: - - urn:alm:descriptor:io.kubernetes:Namespace + path: targetNamespaces - description: Conditions of the components managed by the Cryostat Operator. displayName: Cryostat Conditions path: conditions x-descriptors: - urn:alm:descriptor:io.kubernetes.conditions + - description: A namespace whose workloads Cryostat should be able to connect + and record + displayName: Target Namespace + path: targetNamespaces[0] + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Namespace version: v1beta1 - description: Cryostat allows you to install Cryostat for a single namespace. It contains configuration options for controlling the Deployment of the Cryostat diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 22788a71c..599b07935 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -72,8 +72,6 @@ spec: to access and profile. Defaults to `spec.installNamespace`. displayName: Target Namespaces path: targetNamespaces - x-descriptors: - - urn:alm:descriptor:io.kubernetes:Namespace - description: Use cert-manager to secure in-cluster communication between Cryostat components. Requires cert-manager to be installed. displayName: Enable cert-manager Integration @@ -410,8 +408,6 @@ spec: to access and profile. displayName: Target Namespaces path: targetNamespaces - x-descriptors: - - urn:alm:descriptor:io.kubernetes:Namespace - description: Conditions of the components managed by the Cryostat Operator. displayName: Cryostat Conditions path: conditions diff --git a/config/manifests/targetNamespaces_patch.yaml b/config/manifests/targetNamespaces_patch.yaml index 9e7a73754..8ef6c6eae 100644 --- a/config/manifests/targetNamespaces_patch.yaml +++ b/config/manifests/targetNamespaces_patch.yaml @@ -1,6 +1,16 @@ -- op: replace - path: /spec/customresourcedefinitions/owned/0/specDescriptors/1/path - value: targetNamespaces[0] -- op: replace - path: /spec/customresourcedefinitions/owned/0/statusDescriptors/2/path - value: targetNamespaces[0] +- op: add + path: /spec/customresourcedefinitions/owned/0/specDescriptors/- + value: + description: "A namespace whose workloads Cryostat should be able to connect and record" + displayName: "Target Namespace" + path: targetNamespaces[0] + x-descriptors: + - "urn:alm:descriptor:io.kubernetes:Namespace" +- op: add + path: /spec/customresourcedefinitions/owned/0/statusDescriptors/- + value: + description: "A namespace whose workloads Cryostat should be able to connect and record" + displayName: "Target Namespace" + path: targetNamespaces[0] + x-descriptors: + - "urn:alm:descriptor:io.kubernetes:Namespace" From 8bc2eacc4fc04783f6520b2de69692f9163e7a51 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Mon, 6 Feb 2023 16:14:11 -0500 Subject: [PATCH 12/21] Regenerate bundle --- bundle/manifests/cryostat-operator.clusterserviceversion.yaml | 2 +- .../bases/cryostat-operator.clusterserviceversion.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 6462abf0f..ad7d229d7 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -181,7 +181,7 @@ spec: - urn:alm:descriptor:com.tectonic.ui:number - description: Options to configure the Cryostat application's JMX credentials database. - displayName: Jmx Credentials Database Options + displayName: JMX Credentials Database Options path: jmxCredentialsDatabaseOptions - description: Name of the secret containing the password to encrypt JMX credentials database. diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 599b07935..9c03dfa4a 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -132,7 +132,7 @@ spec: - urn:alm:descriptor:com.tectonic.ui:number - description: Options to configure the Cryostat application's JMX credentials database. - displayName: Jmx Credentials Database Options + displayName: JMX Credentials Database Options path: jmxCredentialsDatabaseOptions - description: Name of the secret containing the password to encrypt JMX credentials database. From 8269e00b920559ce7ed72dfe1ad074f4f21c781e Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Tue, 7 Feb 2023 10:55:20 -0500 Subject: [PATCH 13/21] Add note on multi-tenancy --- api/v1beta1/clustercryostat_types.go | 2 ++ bundle/manifests/cryostat-operator.clusterserviceversion.yaml | 3 ++- bundle/manifests/operator.cryostat.io_clustercryostats.yaml | 3 ++- config/crd/bases/operator.cryostat.io_clustercryostats.yaml | 3 ++- .../bases/cryostat-operator.clusterserviceversion.yaml | 3 ++- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/api/v1beta1/clustercryostat_types.go b/api/v1beta1/clustercryostat_types.go index 4f6c79468..c256ab31e 100644 --- a/api/v1beta1/clustercryostat_types.go +++ b/api/v1beta1/clustercryostat_types.go @@ -43,6 +43,8 @@ import ( // ClusterCryostatSpec defines the desired state of ClusterCryostat. type ClusterCryostatSpec struct { // Namespace where Cryostat should be installed. + // On multi-tenant clusters, we strongly suggest installing Cryostat into + // its own namespace. // +operator-sdk:csv:customresourcedefinitions:type=spec,order=1,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} InstallNamespace string `json:"installNamespace"` // TODO use order to bring to top // List of namespaces whose workloads Cryostat should be diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index ad7d229d7..5fff4d1e2 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -112,7 +112,8 @@ spec: name: "" version: v1 specDescriptors: - - description: Namespace where Cryostat should be installed. + - description: Namespace where Cryostat should be installed. On multi-tenant + clusters, we strongly suggest installing Cryostat into its own namespace. displayName: Install Namespace path: installNamespace x-descriptors: diff --git a/bundle/manifests/operator.cryostat.io_clustercryostats.yaml b/bundle/manifests/operator.cryostat.io_clustercryostats.yaml index e1bd96765..e2e0db8d7 100644 --- a/bundle/manifests/operator.cryostat.io_clustercryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_clustercryostats.yaml @@ -90,7 +90,8 @@ spec: type: object type: array installNamespace: - description: Namespace where Cryostat should be installed. + description: Namespace where Cryostat should be installed. On multi-tenant + clusters, we strongly suggest installing Cryostat into its own namespace. type: string jmxCacheOptions: description: Options to customize the JMX target connections cache diff --git a/config/crd/bases/operator.cryostat.io_clustercryostats.yaml b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml index bb316a72e..9d3625816 100644 --- a/config/crd/bases/operator.cryostat.io_clustercryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml @@ -91,7 +91,8 @@ spec: type: object type: array installNamespace: - description: Namespace where Cryostat should be installed. + description: Namespace where Cryostat should be installed. On multi-tenant + clusters, we strongly suggest installing Cryostat into its own namespace. type: string jmxCacheOptions: description: Options to customize the JMX target connections cache diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 9c03dfa4a..6d98e4d32 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -63,7 +63,8 @@ spec: name: "" version: v1 specDescriptors: - - description: Namespace where Cryostat should be installed. + - description: Namespace where Cryostat should be installed. On multi-tenant + clusters, we strongly suggest installing Cryostat into its own namespace. displayName: Install Namespace path: installNamespace x-descriptors: From ebcb46650fbbb04c927fa8873f56029627a0e9ad Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Wed, 8 Feb 2023 16:57:13 -0500 Subject: [PATCH 14/21] Use common SetupWithManager --- api/v1beta1/clustercryostat_types.go | 2 +- .../controllers/clustercryostat_controller.go | 29 ++------------- internal/controllers/cryostat_controller.go | 30 ++-------------- internal/controllers/reconciler.go | 36 ++++++++++++++++--- 4 files changed, 36 insertions(+), 61 deletions(-) diff --git a/api/v1beta1/clustercryostat_types.go b/api/v1beta1/clustercryostat_types.go index c256ab31e..03fc66a50 100644 --- a/api/v1beta1/clustercryostat_types.go +++ b/api/v1beta1/clustercryostat_types.go @@ -46,7 +46,7 @@ type ClusterCryostatSpec struct { // On multi-tenant clusters, we strongly suggest installing Cryostat into // its own namespace. // +operator-sdk:csv:customresourcedefinitions:type=spec,order=1,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} - InstallNamespace string `json:"installNamespace"` // TODO use order to bring to top + InstallNamespace string `json:"installNamespace"` // List of namespaces whose workloads Cryostat should be // permitted to access and profile. Defaults to `spec.installNamespace`. // +operator-sdk:csv:customresourcedefinitions:type=spec,order=2 diff --git a/internal/controllers/clustercryostat_controller.go b/internal/controllers/clustercryostat_controller.go index 1ff43dc11..b512cf881 100644 --- a/internal/controllers/clustercryostat_controller.go +++ b/internal/controllers/clustercryostat_controller.go @@ -40,17 +40,10 @@ import ( "context" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "github.com/cryostatio/cryostat-operator/internal/controllers/model" - certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" - openshiftv1 "github.com/openshift/api/route/v1" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - netv1 "k8s.io/api/networking/v1" - rbacv1 "k8s.io/api/rbac/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -118,30 +111,12 @@ func (r *ClusterCryostatReconciler) Reconcile(ctx context.Context, request ctrl. } instance := model.FromClusterCryostat(cr) - return r.delegate.ReconcileCryostat(ctx, instance) + return r.delegate.reconcileCryostat(ctx, instance) } // SetupWithManager sets up the controller with the Manager. func (r *ClusterCryostatReconciler) SetupWithManager(mgr ctrl.Manager) error { - c := ctrl.NewControllerManagedBy(mgr). - For(&operatorv1beta1.ClusterCryostat{}) - - // Watch for changes to secondary resources and requeue the owner Cryostat - resources := []client.Object{&appsv1.Deployment{}, &corev1.Service{}, &corev1.Secret{}, &corev1.PersistentVolumeClaim{}, - &corev1.ServiceAccount{}, &rbacv1.Role{}, &rbacv1.RoleBinding{}, &netv1.Ingress{}} - if r.IsOpenShift { - resources = append(resources, &openshiftv1.Route{}) - } - // Can only check this at startup - if r.IsCertManagerInstalled { - resources = append(resources, &certv1.Issuer{}, &certv1.Certificate{}) - } - - for _, resource := range resources { - c = c.Owns(resource) - } - - return c.Complete(r) + return r.delegate.setupWithManager(mgr, &operatorv1beta1.ClusterCryostat{}, r) } func (r *ClusterCryostatReconciler) GetConfig() *ReconcilerConfig { diff --git a/internal/controllers/cryostat_controller.go b/internal/controllers/cryostat_controller.go index 776b89716..4835d535b 100644 --- a/internal/controllers/cryostat_controller.go +++ b/internal/controllers/cryostat_controller.go @@ -40,17 +40,10 @@ import ( "context" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" "github.com/cryostatio/cryostat-operator/internal/controllers/model" - certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" - openshiftv1 "github.com/openshift/api/route/v1" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - netv1 "k8s.io/api/networking/v1" - rbacv1 "k8s.io/api/rbac/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -102,31 +95,12 @@ func (r *CryostatReconciler) Reconcile(ctx context.Context, request ctrl.Request // For namespaced, look up cluster-scoped with same name. If it exists, check the list of target namespaces. // If there's a match, don't process the CR. Emit an event warning the user of the conflict. instance := model.FromCryostat(cr) - return r.delegate.ReconcileCryostat(ctx, instance) + return r.delegate.reconcileCryostat(ctx, instance) } // SetupWithManager sets up the controller with the Manager. func (r *CryostatReconciler) SetupWithManager(mgr ctrl.Manager) error { - c := ctrl.NewControllerManagedBy(mgr). - For(&operatorv1beta1.Cryostat{}) - - // TODO refactor to share between controllers - // Watch for changes to secondary resources and requeue the owner Cryostat - resources := []client.Object{&appsv1.Deployment{}, &corev1.Service{}, &corev1.Secret{}, &corev1.PersistentVolumeClaim{}, - &corev1.ServiceAccount{}, &rbacv1.Role{}, &rbacv1.RoleBinding{}, &netv1.Ingress{}} - if r.IsOpenShift { - resources = append(resources, &openshiftv1.Route{}) - } - // Can only check this at startup - if r.IsCertManagerInstalled { - resources = append(resources, &certv1.Issuer{}, &certv1.Certificate{}) - } - - for _, resource := range resources { - c = c.Owns(resource) - } - - return c.Complete(r) + return r.delegate.setupWithManager(mgr, &operatorv1beta1.Cryostat{}, r) } func (r *CryostatReconciler) GetConfig() *ReconcilerConfig { diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index f8bb851ba..6325a4173 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -50,6 +50,13 @@ import ( "github.com/cryostatio/cryostat-operator/internal/controllers/model" "github.com/go-logr/logr" "github.com/google/go-cmp/cmp" + certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" + openshiftv1 "github.com/openshift/api/route/v1" + securityv1 "github.com/openshift/api/security/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + netv1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -60,10 +67,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" - - securityv1 "github.com/openshift/api/security/v1" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" ) type ReconcilerConfig struct { @@ -130,7 +133,7 @@ var reportsDeploymentConditions = deploymentConditionTypeMap{ operatorv1beta1.ConditionTypeReportsDeploymentReplicaFailure: appsv1.DeploymentReplicaFailure, } -func (r *Reconciler) ReconcileCryostat(ctx context.Context, cr *model.CryostatInstance) (ctrl.Result, error) { +func (r *Reconciler) reconcileCryostat(ctx context.Context, cr *model.CryostatInstance) (ctrl.Result, error) { reqLogger := r.Log.WithValues("Request.Namespace", cr.InstallNamespace, "Request.Name", cr.Name) // Check if this Cryostat is being deleted @@ -271,6 +274,29 @@ func (r *Reconciler) ReconcileCryostat(ctx context.Context, cr *model.CryostatIn return reconcile.Result{}, nil } +func (r *Reconciler) setupWithManager(mgr ctrl.Manager, obj client.Object, + impl reconcile.Reconciler) error { + c := ctrl.NewControllerManagedBy(mgr). + For(obj) + + // Watch for changes to secondary resources and requeue the owner Cryostat + resources := []client.Object{&appsv1.Deployment{}, &corev1.Service{}, &corev1.Secret{}, &corev1.PersistentVolumeClaim{}, + &corev1.ServiceAccount{}, &rbacv1.Role{}, &rbacv1.RoleBinding{}, &netv1.Ingress{}} + if r.IsOpenShift { + resources = append(resources, &openshiftv1.Route{}) + } + // Can only check this at startup + if r.IsCertManagerInstalled { + resources = append(resources, &certv1.Issuer{}, &certv1.Certificate{}) + } + + for _, resource := range resources { + c = c.Owns(resource) + } + + return c.Complete(impl) +} + func (r *Reconciler) reconcileReports(ctx context.Context, reqLogger logr.Logger, cr *model.CryostatInstance, tls *resources.TLSConfig, imageTags *resources.ImageTags, serviceSpecs *resources.ServiceSpecs) (reconcile.Result, error) { reqLogger.Info("Spec", "Reports", cr.Spec.ReportOptions) From a6c1d59b5763c7b0930d9458aedf0fd67548b38f Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Wed, 8 Feb 2023 17:15:00 -0500 Subject: [PATCH 15/21] Rename reconciler interface --- config/manifests/targetNamespaces_patch.yaml | 1 + internal/controllers/clustercryostat_controller.go | 4 ++-- internal/controllers/clustercryostat_controller_test.go | 2 +- internal/controllers/cryostat_controller.go | 4 ++-- internal/controllers/cryostat_controller_test.go | 2 +- internal/controllers/reconciler.go | 6 +++++- internal/controllers/reconciler_test.go | 4 ++-- 7 files changed, 14 insertions(+), 9 deletions(-) diff --git a/config/manifests/targetNamespaces_patch.yaml b/config/manifests/targetNamespaces_patch.yaml index 8ef6c6eae..b669249d4 100644 --- a/config/manifests/targetNamespaces_patch.yaml +++ b/config/manifests/targetNamespaces_patch.yaml @@ -1,3 +1,4 @@ +# Add descriptors for target namespace elements - op: add path: /spec/customresourcedefinitions/owned/0/specDescriptors/- value: diff --git a/internal/controllers/clustercryostat_controller.go b/internal/controllers/clustercryostat_controller.go index b512cf881..fdfaf6f83 100644 --- a/internal/controllers/clustercryostat_controller.go +++ b/internal/controllers/clustercryostat_controller.go @@ -51,8 +51,8 @@ import ( // Generates constants from environment variables at build time //go:generate go run ../tools/const_generator.go -// Verify that *ClusterCryostatReconciler implements ReconcilerInterface. -var _ ReconcilerInterface = (*ClusterCryostatReconciler)(nil) +// Verify that *ClusterCryostatReconciler implements CommonReconciler. +var _ CommonReconciler = (*ClusterCryostatReconciler)(nil) // CryostatReconciler reconciles a Cryostat object type ClusterCryostatReconciler struct { diff --git a/internal/controllers/clustercryostat_controller_test.go b/internal/controllers/clustercryostat_controller_test.go index d35b762e5..2ed983923 100644 --- a/internal/controllers/clustercryostat_controller_test.go +++ b/internal/controllers/clustercryostat_controller_test.go @@ -135,6 +135,6 @@ func (t *cryostatTestInput) expectTargetNamespaces() { Expect(*cr.TargetNamespaceStatus).To(ConsistOf(t.TargetNamespaces)) } -func newClusterCryostatController(config *controllers.ReconcilerConfig) controllers.ReconcilerInterface { +func newClusterCryostatController(config *controllers.ReconcilerConfig) controllers.CommonReconciler { return controllers.NewClusterCryostatReconciler(config) } diff --git a/internal/controllers/cryostat_controller.go b/internal/controllers/cryostat_controller.go index 4835d535b..b151ba5e2 100644 --- a/internal/controllers/cryostat_controller.go +++ b/internal/controllers/cryostat_controller.go @@ -48,8 +48,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -// Verify that *ClusterCryostatReconciler implements ReconcilerInterface. -var _ ReconcilerInterface = (*CryostatReconciler)(nil) +// Verify that *CryostatReconciler implements CommonReconciler. +var _ CommonReconciler = (*CryostatReconciler)(nil) // CryostatReconciler reconciles a Cryostat object type CryostatReconciler struct { diff --git a/internal/controllers/cryostat_controller_test.go b/internal/controllers/cryostat_controller_test.go index 3741db152..5613c4d1b 100644 --- a/internal/controllers/cryostat_controller_test.go +++ b/internal/controllers/cryostat_controller_test.go @@ -50,6 +50,6 @@ var _ = Describe("CryostatController", func() { c.commonTests() }) -func newCryostatController(config *controllers.ReconcilerConfig) controllers.ReconcilerInterface { +func newCryostatController(config *controllers.ReconcilerConfig) controllers.CommonReconciler { return controllers.NewCryostatReconciler(config) } diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index 6325a4173..42207da3d 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -69,6 +69,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +// ReconcilerConfig contains common configuration parameters for +// CommonReconciler implementations type ReconcilerConfig struct { client.Client Log logr.Logger @@ -80,7 +82,9 @@ type ReconcilerConfig struct { common.ReconcilerTLS } -type ReconcilerInterface interface { // TODO rename +// CommonReconciler is an interface for shared behaviour +// between the ClusterCryostat and Cryostat reconcilers +type CommonReconciler interface { reconcile.Reconciler GetConfig() *ReconcilerConfig } diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index 522ba85b5..ee3d5a970 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -74,11 +74,11 @@ import ( type controllerTest struct { clusterScoped bool - constructorFunc func(*controllers.ReconcilerConfig) controllers.ReconcilerInterface + constructorFunc func(*controllers.ReconcilerConfig) controllers.CommonReconciler } type cryostatTestInput struct { - controller controllers.ReconcilerInterface + controller controllers.CommonReconciler objs []runtime.Object test.TestReconcilerConfig *test.TestResources From 97b1ccd658afc9e723e8fa5164af0df325f121a9 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Wed, 8 Feb 2023 18:15:11 -0500 Subject: [PATCH 16/21] Use cluster role instead of role --- api/v1beta1/clustercryostat_types.go | 4 +- ...yostat-operator.clusterserviceversion.yaml | 1 - config/rbac/cryostat_namespaced_role.yaml | 46 ++++++++ config/rbac/role.yaml | 1 - .../controllers/clustercryostat_controller.go | 2 +- .../clustercryostat_controller_test.go | 11 +- internal/controllers/rbac.go | 94 +++------------- internal/controllers/reconciler_test.go | 100 ++++++++++-------- internal/test/resources.go | 12 +-- 9 files changed, 127 insertions(+), 144 deletions(-) create mode 100644 config/rbac/cryostat_namespaced_role.yaml diff --git a/api/v1beta1/clustercryostat_types.go b/api/v1beta1/clustercryostat_types.go index 03fc66a50..f68e6be6f 100644 --- a/api/v1beta1/clustercryostat_types.go +++ b/api/v1beta1/clustercryostat_types.go @@ -56,7 +56,7 @@ type ClusterCryostatSpec struct { } // ClusterCryostatStatus defines the observed state of ClusterCryostat. -type ClusterCryostatStatus struct { // FIXME only conditions are showing in console +type ClusterCryostatStatus struct { // List of namespaces that Cryostat has been configured // and authorized to access and profile. // +operator-sdk:csv:customresourcedefinitions:type=status,order=3 @@ -78,7 +78,7 @@ type ClusterCryostatStatus struct { // FIXME only conditions are showing in cons // +operator-sdk:csv:customresourcedefinitions:resources={{Deployment,v1},{Ingress,v1},{PersistentVolumeClaim,v1},{Secret,v1},{Service,v1},{Route,v1},{ConsoleLink,v1}} // +kubebuilder:printcolumn:name="Application URL",type=string,JSONPath=`.status.applicationUrl` // +kubebuilder:printcolumn:name="Grafana Secret",type=string,JSONPath=`.status.grafanaSecret` -type ClusterCryostat struct { // TODO add cluster-wide API support +type ClusterCryostat struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 5fff4d1e2..da56b1c40 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -1120,7 +1120,6 @@ spec: - rbac.authorization.k8s.io resources: - rolebindings - - roles verbs: - create - delete diff --git a/config/rbac/cryostat_namespaced_role.yaml b/config/rbac/cryostat_namespaced_role.yaml new file mode 100644 index 000000000..fcfb1e4c9 --- /dev/null +++ b/config/rbac/cryostat_namespaced_role.yaml @@ -0,0 +1,46 @@ +# Namespaced Cryostat permissions to be bound by RoleBindings +# in each target namespace +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: cryostat-namespaced +rules: + - verbs: + - get + - list + - watch + apiGroups: + - '' + resources: + - endpoints + - verbs: + - get + apiGroups: + - '' + resources: + - pods + - replicationcontrollers + - verbs: + - get + apiGroups: + - apps + resources: + - replicasets + - deployments + - daemonsets + - statefulsets + - verbs: + - get + apiGroups: + - apps.openshift.io + resources: + - deploymentconfigs + - verbs: + - get + - list + apiGroups: + - route.openshift.io + resources: + - routes diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 15c105823..3e3fdebcb 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -174,7 +174,6 @@ rules: - rbac.authorization.k8s.io resources: - rolebindings - - roles verbs: - create - delete diff --git a/internal/controllers/clustercryostat_controller.go b/internal/controllers/clustercryostat_controller.go index fdfaf6f83..318e25305 100644 --- a/internal/controllers/clustercryostat_controller.go +++ b/internal/controllers/clustercryostat_controller.go @@ -71,7 +71,7 @@ func NewClusterCryostatReconciler(config *ReconcilerConfig) *ClusterCryostatReco // +kubebuilder:rbac:namespace=system,groups="",resources=pods;services;services/finalizers;endpoints;persistentvolumeclaims;events;configmaps;secrets;serviceaccounts,verbs=* // +kubebuilder:rbac:namespace=system,groups="",resources=replicationcontrollers,verbs=get -// +kubebuilder:rbac:namespace=system,groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=create;get;list;update;watch;delete +// +kubebuilder:rbac:namespace=system,groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=create;get;list;update;watch;delete // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=create;get;list;update;watch;delete // +kubebuilder:rbac:groups=authentication.k8s.io,resources=tokenreviews,verbs=create // +kubebuilder:rbac:groups=authorization.k8s.io,resources=selfsubjectaccessreviews,verbs=create diff --git a/internal/controllers/clustercryostat_controller_test.go b/internal/controllers/clustercryostat_controller_test.go index 2ed983923..4234b3958 100644 --- a/internal/controllers/clustercryostat_controller_test.go +++ b/internal/controllers/clustercryostat_controller_test.go @@ -96,8 +96,8 @@ var _ = Describe("ClusterCryostatController", func() { cr := t.NewCryostat() *cr.TargetNamespaceStatus = targetNamespaces t.objs = append(t.objs, cr.Instance, - t.NewRole(targetNamespaces[0]), t.NewRoleBinding(targetNamespaces[0]), - t.NewRole(targetNamespaces[1]), t.NewRoleBinding(targetNamespaces[1])) + t.NewRoleBinding(targetNamespaces[0]), + t.NewRoleBinding(targetNamespaces[1])) }) It("should create the expected main deployment", func() { @@ -111,13 +111,8 @@ var _ = Describe("ClusterCryostatController", func() { It("should remove RBAC from the second namespace", func() { t.reconcileCryostatFully() - role := t.NewRole(targetNamespaces[1]) - err := t.Client.Get(context.Background(), types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, role) - Expect(err).ToNot(BeNil()) - Expect(errors.IsNotFound(err)).To(BeTrue()) - binding := t.NewRoleBinding(targetNamespaces[1]) - err = t.Client.Get(context.Background(), types.NamespacedName{Name: binding.Name, Namespace: binding.Namespace}, binding) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: binding.Name, Namespace: binding.Namespace}, binding) Expect(err).ToNot(BeNil()) Expect(errors.IsNotFound(err)).To(BeTrue()) }) diff --git a/internal/controllers/rbac.go b/internal/controllers/rbac.go index f03cf844b..5a31497c9 100644 --- a/internal/controllers/rbac.go +++ b/internal/controllers/rbac.go @@ -48,6 +48,7 @@ import ( rbacv1 "k8s.io/api/rbac/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -111,63 +112,18 @@ func (r *Reconciler) reconcileServiceAccount(ctx context.Context, cr *model.Cryo return r.createOrUpdateServiceAccount(ctx, sa, cr.Instance, labels, annotations) } -func newRole(cr *model.CryostatInstance, namespace string) *rbacv1.Role { +func newRole(cr *model.CryostatInstance) *rbacv1.Role { return &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name, - Namespace: namespace, + Namespace: cr.InstallNamespace, }, } } func (r *Reconciler) reconcileRole(ctx context.Context, cr *model.CryostatInstance) error { - // TODO convert to ClusterRole? Needs to be separate from existing one used by ClusterRoleBinding. - // If we do, we should delete existing Roles (check for ownership before deleting). - rules := []rbacv1.PolicyRule{ - { - Verbs: []string{"get", "list", "watch"}, - APIGroups: []string{""}, - Resources: []string{"endpoints"}, - }, - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"pods", "replicationcontrollers"}, - }, - { - Verbs: []string{"get"}, - APIGroups: []string{"apps"}, - Resources: []string{"replicasets", "deployments", "daemonsets", "statefulsets"}, - }, - { - Verbs: []string{"get"}, - APIGroups: []string{"apps.openshift.io"}, - Resources: []string{"deploymentconfigs"}, - }, - { - Verbs: []string{"get", "list"}, - APIGroups: []string{"route.openshift.io"}, - Resources: []string{"routes"}, - }, - } - - // Create a Role in each target namespace - for _, ns := range cr.TargetNamespaces { - role := newRole(cr, ns) - err := r.createOrUpdateRole(ctx, role, cr.Instance, rules) - if err != nil { - return err - } - } - // Delete any Roles in target namespaces that are no longer requested - for _, ns := range toDelete(cr) { - role := newRole(cr, ns) - err := r.deleteRole(ctx, role) - if err != nil { - return err - } - } - return nil + // Replaced by a cluster role, clean up any legacy role + return r.cleanUpRole(ctx, cr, newRole(cr)) } func newRoleBinding(cr *model.CryostatInstance, namespace string) *rbacv1.RoleBinding { @@ -194,8 +150,8 @@ func (r *Reconciler) reconcileRoleBinding(ctx context.Context, cr *model.Cryosta binding := newRoleBinding(cr, ns) roleRef := &rbacv1.RoleRef{ APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: newRole(cr, ns).Name, + Kind: "ClusterRole", + Name: "cryostat-operator-cryostat-namespaced", } err := r.createOrUpdateRoleBinding(ctx, binding, cr.Instance, subjects, roleRef) if err != nil { @@ -274,36 +230,18 @@ func (r *Reconciler) createOrUpdateServiceAccount(ctx context.Context, sa *corev return nil } -func (r *Reconciler) createOrUpdateRole(ctx context.Context, role *rbacv1.Role, - owner metav1.Object, rules []rbacv1.PolicyRule) error { - op, err := controllerutil.CreateOrUpdate(ctx, r.Client, role, func() error { - // Update the list of PolicyRules - role.Rules = rules - - // Set the Cryostat CR as controller - if err := controllerutil.SetControllerReference(owner, role, r.Scheme); err != nil { - return err - } - return nil - }) - if err != nil { +func (r *Reconciler) cleanUpRole(ctx context.Context, cr *model.CryostatInstance, role *rbacv1.Role) error { + err := r.Client.Get(ctx, types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, role) + if err != nil && !kerrors.IsNotFound(err) { + r.Log.Error(err, "Could not look up role", "name", role.Name, "namespace", role.Namespace) return err - } - r.Log.Info(fmt.Sprintf("Role %s", op), "name", role.Name, "namespace", role.Namespace) - return nil -} - -func (r *Reconciler) deleteRole(ctx context.Context, role *rbacv1.Role) error { // TODO refactor to reduce duplication - err := r.Client.Delete(ctx, role) - if err != nil { - if kerrors.IsNotFound(err) { - r.Log.Info("No role to delete", "name", role.Name, "namespace", role.Namespace) - return nil + } else if metav1.IsControlledBy(role, cr.Instance) { + err := r.Client.Delete(ctx, role) + if err != nil { + r.Log.Info("Failed to delete role", "name", role.Name, "namespace", role.Namespace) } - r.Log.Error(err, "Could not delete role", "name", role.Name, "namespace", role.Namespace) - return err + r.Log.Info("Role deleted", "name", role.Name, "namespace", role.Namespace) } - r.Log.Info("Role deleted", "name", role.Name, "namespace", role.Namespace) return nil } diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index ee3d5a970..a83ebf99a 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -62,6 +62,7 @@ import ( "k8s.io/client-go/tools/record" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -402,28 +403,34 @@ func (c *controllerTest) commonTests() { }) }) Context("with an existing Role", func() { - var cr *model.CryostatInstance - var oldRole *rbacv1.Role - BeforeEach(func() { - cr = t.NewCryostat() - oldRole = t.OtherRole(t.Namespace) - t.objs = append(t.objs, cr.Instance, oldRole) - }) - It("should update the Role", func() { - t.reconcileCryostatFully() - - role := &rbacv1.Role{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, role) - Expect(err).ToNot(HaveOccurred()) - - Expect(metav1.IsControlledBy(role, cr.Instance)).To(BeTrue()) + var role *rbacv1.Role + Context("created by the operator", func() { + BeforeEach(func() { + cr := t.NewCryostat() + role = t.NewRole() + err := controllerutil.SetControllerReference(cr.Instance, role, test.NewTestScheme()) + Expect(err).ToNot(HaveOccurred()) + t.objs = append(t.objs, cr.Instance, role) + }) + It("should delete the Role", func() { + t.reconcileCryostatFully() - // Labels are unaffected - Expect(role.Labels).To(Equal(oldRole.Labels)) - Expect(role.Annotations).To(Equal(oldRole.Annotations)) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, role) + Expect(err).To(HaveOccurred()) + Expect(kerrors.IsNotFound(err)).To(BeTrue()) + }) + }) + Context("not created by the operator", func() { + BeforeEach(func() { + role = t.OtherRole() + t.objs = append(t.objs, t.NewCryostat().Instance, role) + }) + It("should not delete the Role", func() { + t.reconcileCryostatFully() - // Rules should be fully replaced - Expect(role.Rules).To(Equal(t.NewRole(t.Namespace).Rules)) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, role) + Expect(err).ToNot(HaveOccurred()) + }) }) }) Context("with an existing Role Binding", func() { @@ -1856,28 +1863,34 @@ func (c *controllerTest) commonTests() { }) }) Context("with an existing Role", func() { - var cr *model.CryostatInstance - var oldRole *rbacv1.Role - BeforeEach(func() { - cr = t.NewCryostat() - oldRole = t.OtherRole(t.Namespace) - t.objs = append(t.objs, cr.Instance, oldRole) - }) - It("should update the Role", func() { - t.reconcileCryostatFully() - - role := &rbacv1.Role{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, role) - Expect(err).ToNot(HaveOccurred()) - - Expect(metav1.IsControlledBy(role, cr.Instance)).To(BeTrue()) + var role *rbacv1.Role + Context("created by the operator", func() { + BeforeEach(func() { + cr := t.NewCryostat() + role = t.NewRole() + err := controllerutil.SetControllerReference(cr.Instance, role, test.NewTestScheme()) + Expect(err).ToNot(HaveOccurred()) + t.objs = append(t.objs, cr.Instance, role) + }) + It("should delete the Role", func() { + t.reconcileCryostatFully() - // Labels are unaffected - Expect(role.Labels).To(Equal(oldRole.Labels)) - Expect(role.Annotations).To(Equal(oldRole.Annotations)) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, role) + Expect(err).To(HaveOccurred()) + Expect(kerrors.IsNotFound(err)).To(BeTrue()) + }) + }) + Context("not created by the operator", func() { + BeforeEach(func() { + role = t.OtherRole() + t.objs = append(t.objs, t.NewCryostat().Instance, role) + }) + It("should not delete the Role", func() { + t.reconcileCryostatFully() - // Rules should be fully replaced - Expect(role.Rules).To(Equal(t.NewRole(t.Namespace).Rules)) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, role) + Expect(err).ToNot(HaveOccurred()) + }) }) }) Context("with an existing Role Binding", func() { @@ -2069,13 +2082,6 @@ func (t *cryostatTestInput) expectRBAC() { // Check for Role and RoleBinding in each target namespace Expect(t.TargetNamespaces).ToNot(BeEmpty()) // Sanity check for tests for _, ns := range t.TargetNamespaces { - role := &rbacv1.Role{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: ns}, role) - Expect(err).ToNot(HaveOccurred()) - expectedRole := t.NewRole(ns) - t.checkMetadata(role, expectedRole) - Expect(role.Rules).To(Equal(expectedRole.Rules)) - binding := &rbacv1.RoleBinding{} err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: ns}, binding) Expect(err).ToNot(HaveOccurred()) diff --git a/internal/test/resources.go b/internal/test/resources.go index bd6bc45f5..1856b21f1 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -2354,7 +2354,7 @@ func (r *TestResources) OtherServiceAccount() *corev1.ServiceAccount { } } -func (r *TestResources) NewRole(ns string) *rbacv1.Role { +func (r *TestResources) NewRole() *rbacv1.Role { rules := []rbacv1.PolicyRule{ { Verbs: []string{"get", "list", "watch"}, @@ -2385,17 +2385,17 @@ func (r *TestResources) NewRole(ns string) *rbacv1.Role { return &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: r.Name, - Namespace: ns, + Namespace: r.Namespace, }, Rules: rules, } } -func (r *TestResources) OtherRole(ns string) *rbacv1.Role { +func (r *TestResources) OtherRole() *rbacv1.Role { return &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: r.Name, - Namespace: ns, + Namespace: r.Namespace, Labels: map[string]string{ "test": "label", }, @@ -2445,8 +2445,8 @@ func (r *TestResources) NewRoleBinding(ns string) *rbacv1.RoleBinding { }, RoleRef: rbacv1.RoleRef{ APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: r.Name, + Kind: "ClusterRole", + Name: "cryostat-operator-cryostat-namespaced", }, } } From 8f95aa46be57f388e90f122190c434b71ab6d617 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Wed, 8 Feb 2023 18:35:53 -0500 Subject: [PATCH 17/21] Restore role permission --- bundle/manifests/cryostat-operator.clusterserviceversion.yaml | 1 + config/rbac/role.yaml | 1 + internal/controllers/clustercryostat_controller.go | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index da56b1c40..5fff4d1e2 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -1120,6 +1120,7 @@ spec: - rbac.authorization.k8s.io resources: - rolebindings + - roles verbs: - create - delete diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3e3fdebcb..15c105823 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -174,6 +174,7 @@ rules: - rbac.authorization.k8s.io resources: - rolebindings + - roles verbs: - create - delete diff --git a/internal/controllers/clustercryostat_controller.go b/internal/controllers/clustercryostat_controller.go index 318e25305..fdfaf6f83 100644 --- a/internal/controllers/clustercryostat_controller.go +++ b/internal/controllers/clustercryostat_controller.go @@ -71,7 +71,7 @@ func NewClusterCryostatReconciler(config *ReconcilerConfig) *ClusterCryostatReco // +kubebuilder:rbac:namespace=system,groups="",resources=pods;services;services/finalizers;endpoints;persistentvolumeclaims;events;configmaps;secrets;serviceaccounts,verbs=* // +kubebuilder:rbac:namespace=system,groups="",resources=replicationcontrollers,verbs=get -// +kubebuilder:rbac:namespace=system,groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=create;get;list;update;watch;delete +// +kubebuilder:rbac:namespace=system,groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=create;get;list;update;watch;delete // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=create;get;list;update;watch;delete // +kubebuilder:rbac:groups=authentication.k8s.io,resources=tokenreviews,verbs=create // +kubebuilder:rbac:groups=authorization.k8s.io,resources=selfsubjectaccessreviews,verbs=create From 56176de34987064255238744716d9be3bc7a875b Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Fri, 10 Feb 2023 19:06:26 -0500 Subject: [PATCH 18/21] Add new cluster role to bundle, fix logging --- ...c.authorization.k8s.io_v1_clusterrole.yaml | 43 +++++++++++++++++++ config/rbac/kustomization.yaml | 1 + internal/main.go | 31 +++++++------ 3 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 bundle/manifests/cryostat-operator-cryostat-namespaced_rbac.authorization.k8s.io_v1_clusterrole.yaml diff --git a/bundle/manifests/cryostat-operator-cryostat-namespaced_rbac.authorization.k8s.io_v1_clusterrole.yaml b/bundle/manifests/cryostat-operator-cryostat-namespaced_rbac.authorization.k8s.io_v1_clusterrole.yaml new file mode 100644 index 000000000..173dc63bc --- /dev/null +++ b/bundle/manifests/cryostat-operator-cryostat-namespaced_rbac.authorization.k8s.io_v1_clusterrole.yaml @@ -0,0 +1,43 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: cryostat-operator-cryostat-namespaced +rules: +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - pods + - replicationcontrollers + verbs: + - get +- apiGroups: + - apps + resources: + - replicasets + - deployments + - daemonsets + - statefulsets + verbs: + - get +- apiGroups: + - apps.openshift.io + resources: + - deploymentconfigs + verbs: + - get +- apiGroups: + - route.openshift.io + resources: + - routes + verbs: + - get + - list diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 38ab93436..95bc09ec7 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -4,6 +4,7 @@ resources: - role_binding.yaml - service_account.yaml - cryostat_role.yaml +- cryostat_namespaced_role.yaml - oauth_client.yaml - leader_election_role.yaml - leader_election_role_binding.yaml diff --git a/internal/main.go b/internal/main.go index f6e7e9054..9d9c1cfa6 100644 --- a/internal/main.go +++ b/internal/main.go @@ -136,23 +136,12 @@ func main() { setupLog.Info(fmt.Sprintf("detected %s environment", environment)) certManager := isCertManagerInstalled(apiResources) - - config := &controllers.ReconcilerConfig{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Cryostat"), - Scheme: mgr.GetScheme(), - IsOpenShift: openShift, - IsCertManagerInstalled: certManager, - EventRecorder: mgr.GetEventRecorderFor("cryostat-controller"), - RESTMapper: mgr.GetRESTMapper(), - ReconcilerTLS: common.NewReconcilerTLS(&common.ReconcilerTLSConfig{ - Client: mgr.GetClient(), - }), - } + config := newReconcilerConfig(mgr, "ClusterCryostat", "clustercryostat-controller", openShift, certManager) if err = (controllers.NewClusterCryostatReconciler(config)).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ClusterCryostat") os.Exit(1) } + config = newReconcilerConfig(mgr, "Cryostat", "cryostat-controller", openShift, certManager) if err = (controllers.NewCryostatReconciler(config)).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Cryostat") os.Exit(1) @@ -224,3 +213,19 @@ func lookupGVK(resources []*metav1.APIResourceList, groupVersion string, kind st } return false } + +func newReconcilerConfig(mgr ctrl.Manager, logName string, eventRecorderName string, openShift bool, + certManager bool) *controllers.ReconcilerConfig { + return &controllers.ReconcilerConfig{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName(logName), + Scheme: mgr.GetScheme(), + IsOpenShift: openShift, + IsCertManagerInstalled: certManager, + EventRecorder: mgr.GetEventRecorderFor(eventRecorderName), + RESTMapper: mgr.GetRESTMapper(), + ReconcilerTLS: common.NewReconcilerTLS(&common.ReconcilerTLSConfig{ + Client: mgr.GetClient(), + }), + } +} From 34c84735fbc8da73baf9044bd9bf7899d8237c40 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Mon, 13 Feb 2023 19:35:53 -0500 Subject: [PATCH 19/21] Rename Instance to Object --- internal/controllers/certmanager.go | 18 +- .../clustercryostat_controller_test.go | 4 +- internal/controllers/ingresses.go | 2 +- internal/controllers/model/instance.go | 6 +- internal/controllers/openshift.go | 2 +- internal/controllers/pvc.go | 4 +- internal/controllers/rbac.go | 8 +- internal/controllers/reconciler.go | 22 +- internal/controllers/reconciler_test.go | 188 +++++++++--------- internal/controllers/routes.go | 2 +- internal/controllers/secrets.go | 8 +- internal/controllers/services.go | 6 +- internal/test/resources.go | 4 +- 13 files changed, 137 insertions(+), 137 deletions(-) diff --git a/internal/controllers/certmanager.go b/internal/controllers/certmanager.go index d8c348786..4e42cfec5 100644 --- a/internal/controllers/certmanager.go +++ b/internal/controllers/certmanager.go @@ -67,46 +67,46 @@ func (r *Reconciler) setupTLS(ctx context.Context, cr *model.CryostatInstance) ( return nil, err } if !available { - r.EventRecorder.Event(cr.Instance, corev1.EventTypeWarning, eventCertManagerUnavailableType, eventCertManagerUnavailableMsg) + r.EventRecorder.Event(cr.Object, corev1.EventTypeWarning, eventCertManagerUnavailableType, eventCertManagerUnavailableMsg) return nil, errCertManagerMissing } // Create self-signed issuer used to bootstrap CA - err = r.createOrUpdateIssuer(ctx, resources.NewSelfSignedIssuer(cr), cr.Instance) + err = r.createOrUpdateIssuer(ctx, resources.NewSelfSignedIssuer(cr), cr.Object) if err != nil { return nil, err } // Create CA certificate for Cryostat using the self-signed issuer caCert := resources.NewCryostatCACert(cr) - err = r.createOrUpdateCertificate(ctx, caCert, cr.Instance) + err = r.createOrUpdateCertificate(ctx, caCert, cr.Object) if err != nil { return nil, err } // Create CA issuer using the CA cert just created - err = r.createOrUpdateIssuer(ctx, resources.NewCryostatCAIssuer(cr), cr.Instance) + err = r.createOrUpdateIssuer(ctx, resources.NewCryostatCAIssuer(cr), cr.Object) if err != nil { return nil, err } // Create secret to hold keystore password keystoreSecret := newKeystoreSecret(cr) - err = r.createOrUpdateKeystoreSecret(ctx, keystoreSecret, cr.Instance) + err = r.createOrUpdateKeystoreSecret(ctx, keystoreSecret, cr.Object) if err != nil { return nil, err } // Create a certificate for Cryostat signed by the CA just created cryostatCert := resources.NewCryostatCert(cr, keystoreSecret.Name) - err = r.createOrUpdateCertificate(ctx, cryostatCert, cr.Instance) + err = r.createOrUpdateCertificate(ctx, cryostatCert, cr.Object) if err != nil { return nil, err } // Create a certificate for the reports generator signed by the Cryostat CA reportsCert := resources.NewReportsCert(cr) - err = r.createOrUpdateCertificate(ctx, reportsCert, cr.Instance) + err = r.createOrUpdateCertificate(ctx, reportsCert, cr.Object) if err != nil { return nil, err } @@ -119,7 +119,7 @@ func (r *Reconciler) setupTLS(ctx context.Context, cr *model.CryostatInstance) ( // Create a certificate for Grafana signed by the Cryostat CA if !cr.Spec.Minimal { grafanaCert := resources.NewGrafanaCert(cr) - err = r.createOrUpdateCertificate(ctx, grafanaCert, cr.Instance) + err = r.createOrUpdateCertificate(ctx, grafanaCert, cr.Object) if err != nil { return nil, err } @@ -139,7 +139,7 @@ func (r *Reconciler) setupTLS(ctx context.Context, cr *model.CryostatInstance) ( } // Update owner references of TLS secrets created by cert-manager to ensure proper cleanup - err = r.setCertSecretOwner(ctx, cr.Instance, certificates...) + err = r.setCertSecretOwner(ctx, cr.Object, certificates...) if err != nil { return nil, err } diff --git a/internal/controllers/clustercryostat_controller_test.go b/internal/controllers/clustercryostat_controller_test.go index 4234b3958..dcdc5a7b2 100644 --- a/internal/controllers/clustercryostat_controller_test.go +++ b/internal/controllers/clustercryostat_controller_test.go @@ -61,7 +61,7 @@ var _ = Describe("ClusterCryostatController", func() { BeforeEach(func() { t = c.commonBeforeEach() t.TargetNamespaces = targetNamespaces - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) // Create Namespaces saveNS := t.Namespace for _, ns := range targetNamespaces { @@ -95,7 +95,7 @@ var _ = Describe("ClusterCryostatController", func() { t.TargetNamespaces = targetNamespaces[:1] cr := t.NewCryostat() *cr.TargetNamespaceStatus = targetNamespaces - t.objs = append(t.objs, cr.Instance, + t.objs = append(t.objs, cr.Object, t.NewRoleBinding(targetNamespaces[0]), t.NewRoleBinding(targetNamespaces[1])) }) diff --git a/internal/controllers/ingresses.go b/internal/controllers/ingresses.go index 57ec8da41..f2376e4b6 100644 --- a/internal/controllers/ingresses.go +++ b/internal/controllers/ingresses.go @@ -98,7 +98,7 @@ func (r *Reconciler) reconcileGrafanaIngress(ctx context.Context, cr *model.Cryo func (r *Reconciler) reconcileIngress(ctx context.Context, ingress *netv1.Ingress, cr *model.CryostatInstance, config *operatorv1beta1.NetworkConfiguration) (*url.URL, error) { - ingress, err := r.createOrUpdateIngress(ctx, ingress, cr.Instance, config) + ingress, err := r.createOrUpdateIngress(ctx, ingress, cr.Object, config) if err != nil { return nil, err } diff --git a/internal/controllers/model/instance.go b/internal/controllers/model/instance.go index dfc715ddb..5230e8910 100644 --- a/internal/controllers/model/instance.go +++ b/internal/controllers/model/instance.go @@ -61,7 +61,7 @@ type CryostatInstance struct { // Reference to the common Status properties to both CRD types. Status *operatorv1beta1.CryostatStatus // The actual CR instance as a generic Kubernetes object. - Instance client.Object + Object client.Object } // FromCryostat creates a CryostatInstance from a Cryostat CR @@ -76,7 +76,7 @@ func FromCryostat(cr *operatorv1beta1.Cryostat) *CryostatInstance { Spec: &cr.Spec, Status: &cr.Status, - Instance: cr, + Object: cr, } } @@ -96,6 +96,6 @@ func FromClusterCryostat(cr *operatorv1beta1.ClusterCryostat) *CryostatInstance Spec: &cr.Spec.CryostatSpec, Status: &cr.Status.CryostatStatus, - Instance: cr, + Object: cr, } } diff --git a/internal/controllers/openshift.go b/internal/controllers/openshift.go index c0c4a529c..e5f21f8b0 100644 --- a/internal/controllers/openshift.go +++ b/internal/controllers/openshift.go @@ -104,7 +104,7 @@ func (r *Reconciler) reconcileConsoleLink(ctx context.Context, cr *model.Cryosta } link.Spec.Location = consolev1.NamespaceDashboard link.Spec.NamespaceDashboard = &consolev1.NamespaceDashboardSpec{ - Namespaces: []string{cr.InstallNamespace}, // TODO should this be on targetNamespaces too? + Namespaces: []string{cr.InstallNamespace}, } return nil }) diff --git a/internal/controllers/pvc.go b/internal/controllers/pvc.go index 42fc7b88d..0c4501cb6 100644 --- a/internal/controllers/pvc.go +++ b/internal/controllers/pvc.go @@ -70,12 +70,12 @@ func (r *Reconciler) reconcilePVC(ctx context.Context, cr *model.CryostatInstanc // Look up PVC configuration, applying defaults where needed config := configurePVC(cr) - err := r.createOrUpdatePVC(ctx, pvc, cr.Instance, config) + err := r.createOrUpdatePVC(ctx, pvc, cr.Object, config) if err != nil { // If the API server says the PVC is invalid, emit a warning event // to inform the user. if kerrors.IsInvalid(err) { - r.EventRecorder.Event(cr.Instance, corev1.EventTypeWarning, eventPersistentVolumeClaimInvalidType, err.Error()) + r.EventRecorder.Event(cr.Object, corev1.EventTypeWarning, eventPersistentVolumeClaimInvalidType, err.Error()) } return err } diff --git a/internal/controllers/rbac.go b/internal/controllers/rbac.go index 5a31497c9..c6ec05851 100644 --- a/internal/controllers/rbac.go +++ b/internal/controllers/rbac.go @@ -109,7 +109,7 @@ func (r *Reconciler) reconcileServiceAccount(ctx context.Context, cr *model.Cryo annotations["serviceaccounts.openshift.io/oauth-redirectreference.route"] = string(ref) } - return r.createOrUpdateServiceAccount(ctx, sa, cr.Instance, labels, annotations) + return r.createOrUpdateServiceAccount(ctx, sa, cr.Object, labels, annotations) } func newRole(cr *model.CryostatInstance) *rbacv1.Role { @@ -153,7 +153,7 @@ func (r *Reconciler) reconcileRoleBinding(ctx context.Context, cr *model.Cryosta Kind: "ClusterRole", Name: "cryostat-operator-cryostat-namespaced", } - err := r.createOrUpdateRoleBinding(ctx, binding, cr.Instance, subjects, roleRef) + err := r.createOrUpdateRoleBinding(ctx, binding, cr.Object, subjects, roleRef) if err != nil { return err } @@ -198,7 +198,7 @@ func (r *Reconciler) reconcileClusterRoleBinding(ctx context.Context, cr *model. Name: clusterRoleName, } - return r.createOrUpdateClusterRoleBinding(ctx, binding, cr.Instance, subjects, roleRef) + return r.createOrUpdateClusterRoleBinding(ctx, binding, cr.Object, subjects, roleRef) } func (r *Reconciler) createOrUpdateServiceAccount(ctx context.Context, sa *corev1.ServiceAccount, @@ -235,7 +235,7 @@ func (r *Reconciler) cleanUpRole(ctx context.Context, cr *model.CryostatInstance if err != nil && !kerrors.IsNotFound(err) { r.Log.Error(err, "Could not look up role", "name", role.Name, "namespace", role.Namespace) return err - } else if metav1.IsControlledBy(role, cr.Instance) { + } else if metav1.IsControlledBy(role, cr.Object) { err := r.Client.Delete(ctx, role) if err != nil { r.Log.Info("Failed to delete role", "name", role.Name, "namespace", role.Namespace) diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index 42207da3d..3acce1b0e 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -141,8 +141,8 @@ func (r *Reconciler) reconcileCryostat(ctx context.Context, cr *model.CryostatIn reqLogger := r.Log.WithValues("Request.Namespace", cr.InstallNamespace, "Request.Name", cr.Name) // Check if this Cryostat is being deleted - if cr.Instance.GetDeletionTimestamp() != nil { - if controllerutil.ContainsFinalizer(cr.Instance, cryostatFinalizer) { + if cr.Object.GetDeletionTimestamp() != nil { + if controllerutil.ContainsFinalizer(cr.Object, cryostatFinalizer) { // Perform finalizer logic related to RBAC objects err := r.finalizeRBAC(ctx, cr) if err != nil { @@ -155,7 +155,7 @@ func (r *Reconciler) reconcileCryostat(ctx context.Context, cr *model.CryostatIn return reconcile.Result{}, err } - err = common.RemoveFinalizer(ctx, r.Client, cr.Instance, cryostatFinalizer) + err = common.RemoveFinalizer(ctx, r.Client, cr.Object, cryostatFinalizer) if err != nil { return reconcile.Result{}, err } @@ -165,8 +165,8 @@ func (r *Reconciler) reconcileCryostat(ctx context.Context, cr *model.CryostatIn } // Add our finalizer, so we can clean up Cryostat resources upon deletion - if !controllerutil.ContainsFinalizer(cr.Instance, cryostatFinalizer) { - err := common.AddFinalizer(ctx, r.Client, cr.Instance, cryostatFinalizer) + if !controllerutil.ContainsFinalizer(cr.Object, cryostatFinalizer) { + err := common.AddFinalizer(ctx, r.Client, cr.Object, cryostatFinalizer) if err != nil { return reconcile.Result{}, err } @@ -246,7 +246,7 @@ func (r *Reconciler) reconcileCryostat(ctx context.Context, cr *model.CryostatIn } deployment := resources.NewDeploymentForCR(cr, serviceSpecs, imageTags, tlsConfig, *fsGroup, r.IsOpenShift) - err = r.createOrUpdateDeployment(ctx, deployment, cr.Instance) + err = r.createOrUpdateDeployment(ctx, deployment, cr.Object) if err != nil { return reconcile.Result{}, err } @@ -256,7 +256,7 @@ func (r *Reconciler) reconcileCryostat(ctx context.Context, cr *model.CryostatIn cr.Status.ApplicationURL = serviceSpecs.CoreURL.String() } *cr.TargetNamespaceStatus = cr.TargetNamespaces - err = r.Client.Status().Update(ctx, cr.Instance) + err = r.Client.Status().Update(ctx, cr.Object) if err != nil { return reconcile.Result{}, err } @@ -323,7 +323,7 @@ func (r *Reconciler) reconcileReports(ctx context.Context, reqLogger logr.Logger removeConditionIfPresent(cr, operatorv1beta1.ConditionTypeReportsDeploymentAvailable, operatorv1beta1.ConditionTypeReportsDeploymentProgressing, operatorv1beta1.ConditionTypeReportsDeploymentReplicaFailure) - err := r.Client.Status().Update(ctx, cr.Instance) + err := r.Client.Status().Update(ctx, cr.Object) if err != nil { return reconcile.Result{}, err } @@ -331,7 +331,7 @@ func (r *Reconciler) reconcileReports(ctx context.Context, reqLogger logr.Logger } if desired > 0 { - err = r.createOrUpdateDeployment(ctx, deployment, cr.Instance) + err = r.createOrUpdateDeployment(ctx, deployment, cr.Object) if err != nil { return reconcile.Result{}, err } @@ -407,7 +407,7 @@ func (r *Reconciler) updateCondition(ctx context.Context, cr *model.CryostatInst Reason: reason, Message: message, }) - err := r.Client.Status().Update(ctx, cr.Instance) + err := r.Client.Status().Update(ctx, cr.Object) if err != nil { reqLogger.Error(err, "failed to update condition", "type", condType) } @@ -439,7 +439,7 @@ func (r *Reconciler) updateConditionsFromDeployment(ctx context.Context, cr *mod }) } } - err = r.Client.Status().Update(ctx, cr.Instance) + err = r.Client.Status().Update(ctx, cr.Object) if err != nil { reqLogger.Error(err, "failed to update conditions for deployment", "deployment", deploy.Name) } diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index a83ebf99a..e1c8692cc 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -228,7 +228,7 @@ func (c *controllerTest) commonTests() { } Context("successfully creates required resources", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) expectSuccessful() @@ -244,7 +244,7 @@ func (c *controllerTest) commonTests() { t.Name = names[i] t.Namespace = namespaces[i] t.TargetNamespaces = []string{t.Namespace} - t.objs = append(t.objs, t.NewNamespace(), t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewNamespace(), t.NewCryostat().Object) } }) @@ -267,7 +267,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { t.Minimal = true t.GeneratedPasswords = []string{"credentials_database", "jmx", "keystore"} - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) It("should create certificates", func() { t.expectCertificates() @@ -302,7 +302,7 @@ func (c *controllerTest) commonTests() { }) Context("after cryostat reconciled successfully", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) It("should be idempotent", func() { t.expectIdempotence() @@ -311,7 +311,7 @@ func (c *controllerTest) commonTests() { Context("After a minimal cryostat reconciled successfully", func() { BeforeEach(func() { t.Minimal = true - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) It("should be idempotent", func() { t.expectIdempotence() @@ -330,7 +330,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr = t.NewCryostat() oldDeploy = t.OtherDeployment() - t.objs = append(t.objs, cr.Instance, oldDeploy) + t.objs = append(t.objs, cr.Object, oldDeploy) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -351,7 +351,7 @@ func (c *controllerTest) commonTests() { "app.kubernetes.io/name": "cryostat", "other": "label", })) - Expect(metav1.IsControlledBy(deploy, cr.Instance)).To(BeTrue()) + Expect(metav1.IsControlledBy(deploy, cr.Object)).To(BeTrue()) t.checkMainPodTemplate(deploy, cr) @@ -376,7 +376,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr = t.NewCryostat() oldSA = t.OtherServiceAccount() - t.objs = append(t.objs, cr.Instance, oldSA) + t.objs = append(t.objs, cr.Object, oldSA) }) It("should update the Service Account", func() { t.reconcileCryostatFully() @@ -395,7 +395,7 @@ func (c *controllerTest) commonTests() { "other": "label", })) - Expect(metav1.IsControlledBy(sa, cr.Instance)).To(BeTrue()) + Expect(metav1.IsControlledBy(sa, cr.Object)).To(BeTrue()) Expect(sa.ImagePullSecrets).To(Equal(oldSA.ImagePullSecrets)) Expect(sa.Secrets).To(Equal(oldSA.Secrets)) @@ -408,9 +408,9 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr := t.NewCryostat() role = t.NewRole() - err := controllerutil.SetControllerReference(cr.Instance, role, test.NewTestScheme()) + err := controllerutil.SetControllerReference(cr.Object, role, test.NewTestScheme()) Expect(err).ToNot(HaveOccurred()) - t.objs = append(t.objs, cr.Instance, role) + t.objs = append(t.objs, cr.Object, role) }) It("should delete the Role", func() { t.reconcileCryostatFully() @@ -423,7 +423,7 @@ func (c *controllerTest) commonTests() { Context("not created by the operator", func() { BeforeEach(func() { role = t.OtherRole() - t.objs = append(t.objs, t.NewCryostat().Instance, role) + t.objs = append(t.objs, t.NewCryostat().Object, role) }) It("should not delete the Role", func() { t.reconcileCryostatFully() @@ -439,7 +439,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr = t.NewCryostat() oldBinding = t.OtherRoleBinding(t.Namespace) - t.objs = append(t.objs, cr.Instance, oldBinding) + t.objs = append(t.objs, cr.Object, oldBinding) }) It("should update the Role Binding", func() { t.reconcileCryostatFully() @@ -448,7 +448,7 @@ func (c *controllerTest) commonTests() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, binding) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(binding, cr.Instance)).To(BeTrue()) + Expect(metav1.IsControlledBy(binding, cr.Object)).To(BeTrue()) // Labels are unaffected Expect(binding.Labels).To(Equal(oldBinding.Labels)) @@ -466,7 +466,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr = t.NewCryostat() oldBinding = t.OtherClusterRoleBinding() - t.objs = append(t.objs, cr.Instance, oldBinding) + t.objs = append(t.objs, cr.Object, oldBinding) }) It("should update the Cluster Role Binding", func() { t.reconcileCryostatFully() @@ -493,7 +493,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr = t.NewCryostat() oldSecret = t.OtherGrafanaSecret() - t.objs = append(t.objs, cr.Instance, oldSecret) + t.objs = append(t.objs, cr.Object, oldSecret) }) It("should update the username but not password", func() { t.reconcileCryostatFully() @@ -502,7 +502,7 @@ func (c *controllerTest) commonTests() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: oldSecret.Name, Namespace: t.Namespace}, secret) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(secret, cr.Instance)).To(BeTrue()) + Expect(metav1.IsControlledBy(secret, cr.Object)).To(BeTrue()) // Username should be replaced, but not password expected := t.NewGrafanaSecret() @@ -516,7 +516,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr = t.NewCryostat() oldSecret = t.OtherJMXSecret() - t.objs = append(t.objs, cr.Instance, oldSecret) + t.objs = append(t.objs, cr.Object, oldSecret) }) It("should update the username but not password", func() { t.reconcileCryostatFully() @@ -525,7 +525,7 @@ func (c *controllerTest) commonTests() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: oldSecret.Name, Namespace: t.Namespace}, secret) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(secret, cr.Instance)).To(BeTrue()) + Expect(metav1.IsControlledBy(secret, cr.Object)).To(BeTrue()) // Username should be replaced, but not password expected := t.NewJMXSecret() @@ -539,7 +539,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr = t.NewCryostat() oldSecret = t.OtherCredentialsDatabaseSecret() - t.objs = append(t.objs, cr.Instance, oldSecret) + t.objs = append(t.objs, cr.Object, oldSecret) }) It("should not update password", func() { t.reconcileCryostatFully() @@ -548,7 +548,7 @@ func (c *controllerTest) commonTests() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: oldSecret.Name, Namespace: t.Namespace}, secret) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(secret, cr.Instance)).To(BeTrue()) + Expect(metav1.IsControlledBy(secret, cr.Object)).To(BeTrue()) Expect(secret.StringData["CRYOSTAT_JMX_CREDENTIALS_DB_PASSWORD"]).To(Equal(oldSecret.StringData["CRYOSTAT_JMX_CREDENTIALS_DB_PASSWORD"])) }) }) @@ -560,7 +560,7 @@ func (c *controllerTest) commonTests() { cr = t.NewCryostat() oldCoreRoute = t.OtherCoreRoute() oldGrafanaRoute = t.OtherGrafanaRoute() - t.objs = append(t.objs, cr.Instance, oldCoreRoute, oldGrafanaRoute) + t.objs = append(t.objs, cr.Object, oldCoreRoute, oldGrafanaRoute) }) It("should update the Routes", func() { t.reconcileCryostatFully() @@ -573,7 +573,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { t.Minimal = true t.GeneratedPasswords = []string{"credentials_database", "jmx", "keystore", "grafana"} - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -602,7 +602,7 @@ func (c *controllerTest) commonTests() { }) Context("Switching from a non-minimal to a minimal deployment", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -644,7 +644,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { t.ReportReplicas = 1 cr = t.NewCryostat() - t.objs = append(t.objs, cr.Instance) + t.objs = append(t.objs, cr.Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -731,7 +731,7 @@ func (c *controllerTest) commonTests() { }) Context("Switching from 0 report sidecars to 1", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -755,7 +755,7 @@ func (c *controllerTest) commonTests() { Context("Switching from 1 report sidecar to 2", func() { BeforeEach(func() { t.ReportReplicas = 1 - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -777,7 +777,7 @@ func (c *controllerTest) commonTests() { Context("Switching from 2 report sidecars to 1", func() { BeforeEach(func() { t.ReportReplicas = 2 - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -799,7 +799,7 @@ func (c *controllerTest) commonTests() { Context("Switching from 1 report sidecar to 0", func() { BeforeEach(func() { t.ReportReplicas = 1 - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -828,7 +828,7 @@ func (c *controllerTest) commonTests() { var cr *model.CryostatInstance BeforeEach(func() { cr = t.NewCryostatWithSecrets() - t.objs = append(t.objs, cr.Instance, t.NewTestCertSecret("testCert1"), + t.objs = append(t.objs, cr.Object, t.NewTestCertSecret("testCert1"), t.NewTestCertSecret("testCert2")) }) It("Should add volumes and volumeMounts to deployment", func() { @@ -847,7 +847,7 @@ func (c *controllerTest) commonTests() { }) Context("Adding a certificate to the TrustedCertSecrets list", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance, t.NewTestCertSecret("testCert1"), + t.objs = append(t.objs, t.NewCryostat().Object, t.NewTestCertSecret("testCert1"), t.NewTestCertSecret("testCert2")) }) JustBeforeEach(func() { @@ -878,7 +878,7 @@ func (c *controllerTest) commonTests() { }) Context("Cryostat CR has list of event templates", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithTemplates().Instance, t.NewTemplateConfigMap(), + t.objs = append(t.objs, t.NewCryostatWithTemplates().Object, t.NewTemplateConfigMap(), t.NewOtherTemplateConfigMap()) }) It("Should add volumes and volumeMounts to deployment", func() { @@ -892,7 +892,7 @@ func (c *controllerTest) commonTests() { cr := t.NewCryostatWithTemplates() certManager := false cr.Spec.EnableCertManager = &certManager - t.objs = append(t.objs, cr.Instance, t.NewTemplateConfigMap(), + t.objs = append(t.objs, cr.Object, t.NewTemplateConfigMap(), t.NewOtherTemplateConfigMap()) }) It("Should add volumes and volumeMounts to deployment", func() { @@ -902,7 +902,7 @@ func (c *controllerTest) commonTests() { }) Context("Adding a template to the EventTemplates list", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance, t.NewTemplateConfigMap(), + t.objs = append(t.objs, t.NewCryostat().Object, t.NewTemplateConfigMap(), t.NewOtherTemplateConfigMap()) }) JustBeforeEach(func() { @@ -922,7 +922,7 @@ func (c *controllerTest) commonTests() { }) Context("with custom PVC spec overriding all defaults", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithPVCSpec().Instance) + t.objs = append(t.objs, t.NewCryostatWithPVCSpec().Object) }) It("should create the PVC with requested spec", func() { t.expectPVC(t.NewCustomPVC()) @@ -930,7 +930,7 @@ func (c *controllerTest) commonTests() { }) Context("with custom PVC spec overriding some defaults", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithPVCSpecSomeDefault().Instance) + t.objs = append(t.objs, t.NewCryostatWithPVCSpecSomeDefault().Object) }) It("should create the PVC with requested spec", func() { t.expectPVC(t.NewCustomPVCSomeDefault()) @@ -938,7 +938,7 @@ func (c *controllerTest) commonTests() { }) Context("with custom PVC config with no spec", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithPVCLabelsOnly().Instance) + t.objs = append(t.objs, t.NewCryostatWithPVCLabelsOnly().Object) }) It("should create the PVC with requested label", func() { t.expectPVC(t.NewDefaultPVCWithLabel()) @@ -948,7 +948,7 @@ func (c *controllerTest) commonTests() { var oldPVC *corev1.PersistentVolumeClaim BeforeEach(func() { oldPVC = t.NewDefaultPVC() - t.objs = append(t.objs, t.NewCryostatWithPVCSpec().Instance, oldPVC) + t.objs = append(t.objs, t.NewCryostatWithPVCSpec().Object, oldPVC) }) Context("that successfully updates", func() { BeforeEach(func() { @@ -996,7 +996,7 @@ func (c *controllerTest) commonTests() { }) Context("with custom EmptyDir config", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithDefaultEmptyDir().Instance) + t.objs = append(t.objs, t.NewCryostatWithDefaultEmptyDir().Object) }) It("should create the EmptyDir with default specs", func() { t.expectEmptyDir(t.NewDefaultEmptyDir()) @@ -1007,7 +1007,7 @@ func (c *controllerTest) commonTests() { }) Context("with custom EmptyDir config with requested spec", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithEmptyDirSpec().Instance) + t.objs = append(t.objs, t.NewCryostatWithEmptyDirSpec().Object) }) It("should create the EmptyDir with requested specs", func() { t.expectEmptyDir(t.NewEmptyDirWithSpec()) @@ -1020,7 +1020,7 @@ func (c *controllerTest) commonTests() { var mainDeploy, reportsDeploy *appsv1.Deployment BeforeEach(func() { t.ReportReplicas = 1 - t.objs = append(t.objs, t.NewCryostatWithReportsSvc().Instance) + t.objs = append(t.objs, t.NewCryostatWithReportsSvc().Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1138,7 +1138,7 @@ func (c *controllerTest) commonTests() { }) Context("when deleted", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1167,7 +1167,7 @@ func (c *controllerTest) commonTests() { }) Context("on OpenShift", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1212,7 +1212,7 @@ func (c *controllerTest) commonTests() { Context("with restricted SCC", func() { BeforeEach(func() { t.objs = []runtime.Object{ - t.NewCryostat().Instance, t.NewNamespaceWithSCCSupGroups(), t.NewApiServer(), + t.NewCryostat().Object, t.NewNamespaceWithSCCSupGroups(), t.NewApiServer(), } }) It("should set fsGroup to value derived from namespace", func() { @@ -1262,7 +1262,7 @@ func (c *controllerTest) commonTests() { Context("with cert-manager disabled in CR", func() { BeforeEach(func() { t.TLS = false - t.objs = append(t.objs, t.NewCryostatCertManagerDisabled().Instance) + t.objs = append(t.objs, t.NewCryostatCertManagerDisabled().Object) }) It("should create deployment and set owner", func() { t.expectDeployment() @@ -1280,7 +1280,7 @@ func (c *controllerTest) commonTests() { }) Context("with cert-manager not configured in CR", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatCertManagerUndefined().Instance) + t.objs = append(t.objs, t.NewCryostatCertManagerUndefined().Object) }) It("should create deployment and set owner", func() { t.expectDeployment() @@ -1302,7 +1302,7 @@ func (c *controllerTest) commonTests() { disableTLS := true t.EnvDisableTLS = &disableTLS t.TLS = false - t.objs = append(t.objs, t.NewCryostatCertManagerUndefined().Instance) + t.objs = append(t.objs, t.NewCryostatCertManagerUndefined().Object) }) It("should create deployment and set owner", func() { t.expectDeployment() @@ -1325,7 +1325,7 @@ func (c *controllerTest) commonTests() { }) Context("Disable cert-manager after being enabled", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1353,7 +1353,7 @@ func (c *controllerTest) commonTests() { Context("Enable cert-manager after being disabled", func() { BeforeEach(func() { t.TLS = false - t.objs = append(t.objs, t.NewCryostatCertManagerDisabled().Instance) + t.objs = append(t.objs, t.NewCryostatCertManagerDisabled().Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1388,7 +1388,7 @@ func (c *controllerTest) commonTests() { }) Context("and enabled", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) JustBeforeEach(func() { _, err := t.reconcile() @@ -1408,7 +1408,7 @@ func (c *controllerTest) commonTests() { Context("and disabled", func() { BeforeEach(func() { t.TLS = false - t.objs = append(t.objs, t.NewCryostatCertManagerDisabled().Instance) + t.objs = append(t.objs, t.NewCryostatCertManagerDisabled().Object) }) JustBeforeEach(func() { _, err := t.reconcile() @@ -1430,7 +1430,7 @@ func (c *controllerTest) commonTests() { }) Context("containing core config", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithCoreSvc().Instance) + t.objs = append(t.objs, t.NewCryostatWithCoreSvc().Object) }) It("should create the service as described", func() { t.checkService(t.Name, t.NewCustomizedCoreService()) @@ -1438,7 +1438,7 @@ func (c *controllerTest) commonTests() { }) Context("containing grafana config", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithGrafanaSvc().Instance) + t.objs = append(t.objs, t.NewCryostatWithGrafanaSvc().Object) }) It("should create the service as described", func() { t.checkService(t.Name+"-grafana", t.NewCustomizedGrafanaService()) @@ -1447,7 +1447,7 @@ func (c *controllerTest) commonTests() { Context("containing reports config", func() { BeforeEach(func() { t.ReportReplicas = 1 - t.objs = append(t.objs, t.NewCryostatWithReportsSvc().Instance) + t.objs = append(t.objs, t.NewCryostatWithReportsSvc().Object) }) It("should create the service as described", func() { t.checkService(t.Name+"-reports", t.NewCustomizedReportsService()) @@ -1456,7 +1456,7 @@ func (c *controllerTest) commonTests() { Context("and existing services", func() { var cr *model.CryostatInstance BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) JustBeforeEach(func() { // Fetch the current Cryostat CR @@ -1501,7 +1501,7 @@ func (c *controllerTest) commonTests() { }) Context("containing MaxWsConnections", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithWsConnectionsSpec().Instance) + t.objs = append(t.objs, t.NewCryostatWithWsConnectionsSpec().Object) }) It("should set max WebSocket connections", func() { t.checkCoreHasEnvironmentVariables(t.NewWsConnectionsEnv()) @@ -1509,7 +1509,7 @@ func (c *controllerTest) commonTests() { }) Context("containing SubProcessMaxHeapSize", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithReportSubprocessHeapSpec().Instance) + t.objs = append(t.objs, t.NewCryostatWithReportSubprocessHeapSpec().Object) }) It("should set report subprocess max heap size", func() { t.checkCoreHasEnvironmentVariables(t.NewReportSubprocessHeapEnv()) @@ -1517,7 +1517,7 @@ func (c *controllerTest) commonTests() { }) Context("containing JmxCacheOptions", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithJmxCacheOptionsSpec().Instance) + t.objs = append(t.objs, t.NewCryostatWithJmxCacheOptionsSpec().Object) }) It("should set JMX cache options", func() { t.checkCoreHasEnvironmentVariables(t.NewJmxCacheOptionsEnv()) @@ -1527,7 +1527,7 @@ func (c *controllerTest) commonTests() { Context("with resource requirements", func() { Context("fully specified", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithResources().Instance) + t.objs = append(t.objs, t.NewCryostatWithResources().Object) }) It("should create expected deployment", func() { t.expectDeployment() @@ -1535,7 +1535,7 @@ func (c *controllerTest) commonTests() { }) Context("with low limits", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithLowResourceLimit().Instance) + t.objs = append(t.objs, t.NewCryostatWithLowResourceLimit().Object) }) It("should create expected deployment", func() { t.expectDeployment() @@ -1548,7 +1548,7 @@ func (c *controllerTest) commonTests() { }) Context("containing core config", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithCoreNetworkOptions().Instance) + t.objs = append(t.objs, t.NewCryostatWithCoreNetworkOptions().Object) }) It("should create the route as described", func() { t.checkRoute(t.NewCustomCoreRoute()) @@ -1556,7 +1556,7 @@ func (c *controllerTest) commonTests() { }) Context("containing grafana config", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithGrafanaNetworkOptions().Instance) + t.objs = append(t.objs, t.NewCryostatWithGrafanaNetworkOptions().Object) }) It("should create the route as described", func() { t.checkRoute(t.NewCustomGrafanaRoute()) @@ -1565,7 +1565,7 @@ func (c *controllerTest) commonTests() { }) Context("Cryostat CR has authorization properties", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithAuthProperties().Instance, t.NewAuthPropertiesConfigMap(), t.NewAuthClusterRole()) + t.objs = append(t.objs, t.NewCryostatWithAuthProperties().Object, t.NewAuthPropertiesConfigMap(), t.NewAuthClusterRole()) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1580,7 +1580,7 @@ func (c *controllerTest) commonTests() { }) Context("containing Cryostat security options", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithSecurityOptions().Instance) + t.objs = append(t.objs, t.NewCryostatWithSecurityOptions().Object) }) It("should add security context as described", func() { t.checkMainDeployment() @@ -1589,7 +1589,7 @@ func (c *controllerTest) commonTests() { Context("containing Report security options", func() { Context("with 0 report replica", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions().Instance) + t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions().Object) }) It("should add security context as described", func() { t.expectNoReportsDeployment() @@ -1599,7 +1599,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { t.ReportReplicas = 1 cr := t.NewCryostatWithReportSecurityOptions() - t.objs = append(t.objs, cr.Instance) + t.objs = append(t.objs, cr.Object) }) It("should add security context as described", func() { t.checkReportsDeployment() @@ -1610,7 +1610,7 @@ func (c *controllerTest) commonTests() { }) Context("with Scheduling options", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithScheduling().Instance) + t.objs = append(t.objs, t.NewCryostatWithScheduling().Object) }) It("should configure deployment appropriately", func() { t.expectDeployment() @@ -1619,7 +1619,7 @@ func (c *controllerTest) commonTests() { }) Context("with built-in target discovery mechanism disabled", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithBuiltInDiscoveryDisabled().Instance) + t.objs = append(t.objs, t.NewCryostatWithBuiltInDiscoveryDisabled().Object) }) It("should configure deployment appropriately", func() { t.expectDeployment() @@ -1627,7 +1627,7 @@ func (c *controllerTest) commonTests() { }) Context("with secret provided for database password", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithDatabaseSecretProvided().Instance) + t.objs = append(t.objs, t.NewCryostatWithDatabaseSecretProvided().Object) }) It("should configure deployment appropriately", func() { t.expectDeployment() @@ -1660,7 +1660,7 @@ func (c *controllerTest) commonTests() { }) Context("with TLS ingress", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithIngress().Instance) + t.objs = append(t.objs, t.NewCryostatWithIngress().Object) }) It("should create ingresses", func() { t.expectIngresses() @@ -1679,7 +1679,7 @@ func (c *controllerTest) commonTests() { Context("with non-TLS ingress", func() { BeforeEach(func() { t.ExternalTLS = false - t.objs = append(t.objs, t.NewCryostatWithIngress().Instance) + t.objs = append(t.objs, t.NewCryostatWithIngress().Object) }) It("should create ingresses", func() { t.expectIngresses() @@ -1697,7 +1697,7 @@ func (c *controllerTest) commonTests() { }) Context("no ingress configuration is provided", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Instance) + t.objs = append(t.objs, t.NewCryostat().Object) }) It("should not create ingresses or routes", func() { t.reconcileCryostatFully() @@ -1713,7 +1713,7 @@ func (c *controllerTest) commonTests() { cr = t.NewCryostatWithIngress() oldCoreIngress = t.OtherCoreIngress() oldGrafanaIngress = t.OtherGrafanaIngress() - t.objs = append(t.objs, cr.Instance, oldCoreIngress, oldGrafanaIngress) + t.objs = append(t.objs, cr.Object, oldCoreIngress, oldGrafanaIngress) }) It("should update the Ingresses", func() { t.expectIngresses() @@ -1723,7 +1723,7 @@ func (c *controllerTest) commonTests() { var cr *model.CryostatInstance BeforeEach(func() { cr = t.NewCryostatWithIngress() - t.objs = append(t.objs, cr.Instance) + t.objs = append(t.objs, cr.Object) }) It("should only create specified ingresses", func() { c := t.getCryostatInstance() @@ -1749,7 +1749,7 @@ func (c *controllerTest) commonTests() { var cr *model.CryostatInstance BeforeEach(func() { cr = t.NewCryostatWithIngress() - t.objs = append(t.objs, cr.Instance) + t.objs = append(t.objs, cr.Object) }) It("should only create specified ingresses", func() { c := t.getCryostatInstance() @@ -1772,7 +1772,7 @@ func (c *controllerTest) commonTests() { }) Context("Cryostat CR has authorization properties", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithAuthProperties().Instance, t.NewAuthPropertiesConfigMap(), t.NewAuthClusterRole()) + t.objs = append(t.objs, t.NewCryostatWithAuthProperties().Object, t.NewAuthPropertiesConfigMap(), t.NewAuthClusterRole()) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1785,7 +1785,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { t.ReportReplicas = 1 cr := t.NewCryostatWithIngress() - t.objs = append(t.objs, cr.Instance) + t.objs = append(t.objs, cr.Object) }) JustBeforeEach(func() { t.reconcileCryostatFully() @@ -1804,7 +1804,7 @@ func (c *controllerTest) commonTests() { }) Context("containing Cryostat security options", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithSecurityOptions().Instance) + t.objs = append(t.objs, t.NewCryostatWithSecurityOptions().Object) }) It("should add security context as described", func() { t.checkMainDeployment() @@ -1813,7 +1813,7 @@ func (c *controllerTest) commonTests() { Context("containing Report security options", func() { Context("with 0 report replica", func() { BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions().Instance) + t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions().Object) }) It("should add security context as described", func() { t.expectNoReportsDeployment() @@ -1822,7 +1822,7 @@ func (c *controllerTest) commonTests() { Context("with 1 report replicas", func() { BeforeEach(func() { t.ReportReplicas = 1 - t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions().Instance) + t.objs = append(t.objs, t.NewCryostatWithReportSecurityOptions().Object) }) It("should add security context as described", func() { t.checkReportsDeployment() @@ -1837,7 +1837,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr = t.NewCryostat() oldSA = t.OtherServiceAccount() - t.objs = append(t.objs, cr.Instance, oldSA) + t.objs = append(t.objs, cr.Object, oldSA) }) It("should update the Service Account", func() { t.reconcileCryostatFully() @@ -1855,7 +1855,7 @@ func (c *controllerTest) commonTests() { "other": "label", })) - Expect(metav1.IsControlledBy(sa, cr.Instance)).To(BeTrue()) + Expect(metav1.IsControlledBy(sa, cr.Object)).To(BeTrue()) Expect(sa.ImagePullSecrets).To(Equal(oldSA.ImagePullSecrets)) Expect(sa.Secrets).To(Equal(oldSA.Secrets)) @@ -1868,9 +1868,9 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr := t.NewCryostat() role = t.NewRole() - err := controllerutil.SetControllerReference(cr.Instance, role, test.NewTestScheme()) + err := controllerutil.SetControllerReference(cr.Object, role, test.NewTestScheme()) Expect(err).ToNot(HaveOccurred()) - t.objs = append(t.objs, cr.Instance, role) + t.objs = append(t.objs, cr.Object, role) }) It("should delete the Role", func() { t.reconcileCryostatFully() @@ -1883,7 +1883,7 @@ func (c *controllerTest) commonTests() { Context("not created by the operator", func() { BeforeEach(func() { role = t.OtherRole() - t.objs = append(t.objs, t.NewCryostat().Instance, role) + t.objs = append(t.objs, t.NewCryostat().Object, role) }) It("should not delete the Role", func() { t.reconcileCryostatFully() @@ -1899,7 +1899,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr = t.NewCryostat() oldBinding = t.OtherRoleBinding(t.Namespace) - t.objs = append(t.objs, cr.Instance, oldBinding) + t.objs = append(t.objs, cr.Object, oldBinding) }) It("should update the Role Binding", func() { t.reconcileCryostatFully() @@ -1908,7 +1908,7 @@ func (c *controllerTest) commonTests() { err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, binding) Expect(err).ToNot(HaveOccurred()) - Expect(metav1.IsControlledBy(binding, cr.Instance)).To(BeTrue()) + Expect(metav1.IsControlledBy(binding, cr.Object)).To(BeTrue()) // Labels are unaffected Expect(binding.Labels).To(Equal(oldBinding.Labels)) @@ -1926,7 +1926,7 @@ func (c *controllerTest) commonTests() { BeforeEach(func() { cr = t.NewCryostat() oldBinding = t.OtherClusterRoleBinding() - t.objs = append(t.objs, cr.Instance, oldBinding) + t.objs = append(t.objs, cr.Object, oldBinding) }) It("should update the Cluster Role Binding", func() { t.reconcileCryostatFully() @@ -1998,7 +1998,7 @@ func (t *cryostatTestInput) reconcileDeletedCryostat() { cr := t.getCryostatInstance() delTime := metav1.Unix(0, 1598045501618*int64(time.Millisecond)) - cr.Instance.SetDeletionTimestamp(&delTime) + cr.Object.SetDeletionTimestamp(&delTime) t.updateCryostatInstance(cr) // Reconcile again @@ -2011,7 +2011,7 @@ func (t *cryostatTestInput) checkMetadata(object metav1.Object, expected metav1. Expect(object.GetLabels()).To(Equal(expected.GetLabels())) Expect(object.GetAnnotations()).To(Equal(expected.GetAnnotations())) Expect(object.GetOwnerReferences()).To(HaveLen(1)) - Expect(metav1.IsControlledBy(object, t.getCryostatInstance().Instance)) + Expect(metav1.IsControlledBy(object, t.getCryostatInstance().Object)) } func (t *cryostatTestInput) expectNoCryostat() { @@ -2331,7 +2331,7 @@ func (t *cryostatTestInput) expectIdempotence() { func (t *cryostatTestInput) expectCryostatFinalizerPresent() { cr := t.getCryostatInstance() - Expect(cr.Instance.GetFinalizers()).To(ContainElement("operator.cryostat.io/cryostat.finalizer")) + Expect(cr.Object.GetFinalizers()).To(ContainElement("operator.cryostat.io/cryostat.finalizer")) } func (t *cryostatTestInput) checkGrafanaService() { @@ -2437,7 +2437,7 @@ func (t *cryostatTestInput) checkMainDeployment() { "component": "cryostat", "app.kubernetes.io/name": "cryostat", })) - Expect(metav1.IsControlledBy(deployment, cr.Instance)).To(BeTrue()) + Expect(metav1.IsControlledBy(deployment, cr.Object)).To(BeTrue()) Expect(deployment.Spec.Selector).To(Equal(t.NewMainDeploymentSelector())) Expect(deployment.Spec.Replicas).ToNot(BeNil()) Expect(*deployment.Spec.Replicas).To(Equal(int32(1))) @@ -2527,7 +2527,7 @@ func (t *cryostatTestInput) checkReportsDeployment() { "component": "reports", "app.kubernetes.io/name": "cryostat-reports", })) - Expect(metav1.IsControlledBy(deployment, cr.Instance)).To(BeTrue()) + Expect(metav1.IsControlledBy(deployment, cr.Object)).To(BeTrue()) Expect(deployment.Spec.Selector).To(Equal(t.NewReportsDeploymentSelector())) Expect(deployment.Spec.Replicas).ToNot(BeNil()) Expect(*deployment.Spec.Replicas).To(Equal(t.ReportReplicas)) @@ -2757,7 +2757,7 @@ func (t *cryostatTestInput) lookupCryostatInstance() (*model.CryostatInstance, e } func (t *cryostatTestInput) updateCryostatInstance(cr *model.CryostatInstance) { - err := t.Client.Update(context.Background(), cr.Instance) + err := t.Client.Update(context.Background(), cr.Object) Expect(err).ToNot(HaveOccurred()) } diff --git a/internal/controllers/routes.go b/internal/controllers/routes.go index f2136778f..fdb666bd8 100644 --- a/internal/controllers/routes.go +++ b/internal/controllers/routes.go @@ -105,7 +105,7 @@ func (r *Reconciler) reconcileRoute(ctx context.Context, route *routev1.Route, s if err != nil { return nil, err } - route, err = r.createOrUpdateRoute(ctx, route, cr.Instance, svc, port, tls, config) + route, err = r.createOrUpdateRoute(ctx, route, cr.Object, svc, port, tls, config) if err != nil { return nil, err } diff --git a/internal/controllers/secrets.go b/internal/controllers/secrets.go index 514edad97..ed0ae2889 100644 --- a/internal/controllers/secrets.go +++ b/internal/controllers/secrets.go @@ -73,7 +73,7 @@ func (r *Reconciler) reconcileGrafanaSecret(ctx context.Context, cr *model.Cryos } secretName = "" } else { - err := r.createOrUpdateSecret(ctx, secret, cr.Instance, func() error { + err := r.createOrUpdateSecret(ctx, secret, cr.Object, func() error { if secret.StringData == nil { secret.StringData = map[string]string{} } @@ -93,7 +93,7 @@ func (r *Reconciler) reconcileGrafanaSecret(ctx context.Context, cr *model.Cryos // Set the Grafana secret in the CR status cr.Status.GrafanaSecret = secretName - return r.Client.Status().Update(ctx, cr.Instance) + return r.Client.Status().Update(ctx, cr.Object) } // jmxSecretNameSuffix is the suffix to be appended to the name of a @@ -114,7 +114,7 @@ func (r *Reconciler) reconcileJMXSecret(ctx context.Context, cr *model.CryostatI }, } - return r.createOrUpdateSecret(ctx, secret, cr.Instance, func() error { + return r.createOrUpdateSecret(ctx, secret, cr.Object, func() error { if secret.StringData == nil { secret.StringData = map[string]string{} } @@ -148,7 +148,7 @@ func (r *Reconciler) reconcileDatabaseSecret(ctx context.Context, cr *model.Cryo return nil // Do not delete default secret to allow reverting to use default if needed } - return r.createOrUpdateSecret(ctx, secret, cr.Instance, func() error { + return r.createOrUpdateSecret(ctx, secret, cr.Object, func() error { if secret.StringData == nil { secret.StringData = map[string]string{} } diff --git a/internal/controllers/services.go b/internal/controllers/services.go index 3bf6a0edc..65a7a6504 100644 --- a/internal/controllers/services.go +++ b/internal/controllers/services.go @@ -63,7 +63,7 @@ func (r *Reconciler) reconcileCoreService(ctx context.Context, cr *model.Cryosta } config := configureCoreService(cr) - err := r.createOrUpdateService(ctx, svc, cr.Instance, &config.ServiceConfig, func() error { + err := r.createOrUpdateService(ctx, svc, cr.Object, &config.ServiceConfig, func() error { svc.Spec.Selector = map[string]string{ "app": cr.Name, "component": "cryostat", @@ -110,7 +110,7 @@ func (r *Reconciler) reconcileGrafanaService(ctx context.Context, cr *model.Cryo } } else { config := configureGrafanaService(cr) - err := r.createOrUpdateService(ctx, svc, cr.Instance, &config.ServiceConfig, func() error { + err := r.createOrUpdateService(ctx, svc, cr.Object, &config.ServiceConfig, func() error { svc.Spec.Selector = map[string]string{ "app": cr.Name, "component": "cryostat", @@ -150,7 +150,7 @@ func (r *Reconciler) reconcileReportsService(ctx context.Context, cr *model.Cryo // Delete service if it exists return r.deleteService(ctx, svc) } - err := r.createOrUpdateService(ctx, svc, cr.Instance, &config.ServiceConfig, func() error { + err := r.createOrUpdateService(ctx, svc, cr.Object, &config.ServiceConfig, func() error { svc.Spec.Selector = map[string]string{ "app": cr.Name, "component": "reports", diff --git a/internal/test/resources.go b/internal/test/resources.go index 1856b21f1..ba0b99396 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -159,7 +159,7 @@ func (r *TestResources) ConvertNamespacedToModel(cr *operatorv1beta1.Cryostat) * TargetNamespaceStatus: &targetNS, Spec: &cr.Spec, Status: &cr.Status, - Instance: cr, + Object: cr, } } @@ -171,7 +171,7 @@ func (r *TestResources) ConvertClusterToModel(cr *operatorv1beta1.ClusterCryosta TargetNamespaceStatus: &cr.Status.TargetNamespaces, Spec: &cr.Spec.CryostatSpec, Status: &cr.Status.CryostatStatus, - Instance: cr, + Object: cr, } } From 7949ad033c9165673dfce5c2bbd114d66f70b414 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Tue, 21 Feb 2023 16:19:57 -0500 Subject: [PATCH 20/21] Don't default to installNamespace --- api/v1beta1/clustercryostat_types.go | 4 +++- .../cryostat-operator.clusterserviceversion.yaml | 2 +- .../operator.cryostat.io_clustercryostats.yaml | 2 +- .../operator.cryostat.io_clustercryostats.yaml | 2 +- .../cryostat-operator.clusterserviceversion.yaml | 2 +- .../clustercryostat_controller_test.go | 16 ++++++++++++++++ internal/controllers/model/instance.go | 7 +------ 7 files changed, 24 insertions(+), 11 deletions(-) diff --git a/api/v1beta1/clustercryostat_types.go b/api/v1beta1/clustercryostat_types.go index f68e6be6f..7a50044c5 100644 --- a/api/v1beta1/clustercryostat_types.go +++ b/api/v1beta1/clustercryostat_types.go @@ -48,7 +48,8 @@ type ClusterCryostatSpec struct { // +operator-sdk:csv:customresourcedefinitions:type=spec,order=1,xDescriptors={"urn:alm:descriptor:io.kubernetes:Namespace"} InstallNamespace string `json:"installNamespace"` // List of namespaces whose workloads Cryostat should be - // permitted to access and profile. Defaults to `spec.installNamespace`. + // permitted to access and profile. + // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,order=2 TargetNamespaces []string `json:"targetNamespaces,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec @@ -59,6 +60,7 @@ type ClusterCryostatSpec struct { type ClusterCryostatStatus struct { // List of namespaces that Cryostat has been configured // and authorized to access and profile. + // +optional // +operator-sdk:csv:customresourcedefinitions:type=status,order=3 TargetNamespaces []string `json:"targetNamespaces,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=status diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 5fff4d1e2..256ecc41a 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -119,7 +119,7 @@ spec: x-descriptors: - urn:alm:descriptor:io.kubernetes:Namespace - description: List of namespaces whose workloads Cryostat should be permitted - to access and profile. Defaults to `spec.installNamespace`. + to access and profile. displayName: Target Namespaces path: targetNamespaces - description: Use cert-manager to secure in-cluster communication between Cryostat diff --git a/bundle/manifests/operator.cryostat.io_clustercryostats.yaml b/bundle/manifests/operator.cryostat.io_clustercryostats.yaml index e2e0db8d7..668838cda 100644 --- a/bundle/manifests/operator.cryostat.io_clustercryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_clustercryostats.yaml @@ -4398,7 +4398,7 @@ spec: type: object targetNamespaces: description: List of namespaces whose workloads Cryostat should be - permitted to access and profile. Defaults to `spec.installNamespace`. + permitted to access and profile. items: type: string type: array diff --git a/config/crd/bases/operator.cryostat.io_clustercryostats.yaml b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml index 9d3625816..d24bfde83 100644 --- a/config/crd/bases/operator.cryostat.io_clustercryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml @@ -4399,7 +4399,7 @@ spec: type: object targetNamespaces: description: List of namespaces whose workloads Cryostat should be - permitted to access and profile. Defaults to `spec.installNamespace`. + permitted to access and profile. items: type: string type: array diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 6d98e4d32..1ab2a4799 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -70,7 +70,7 @@ spec: x-descriptors: - urn:alm:descriptor:io.kubernetes:Namespace - description: List of namespaces whose workloads Cryostat should be permitted - to access and profile. Defaults to `spec.installNamespace`. + to access and profile. displayName: Target Namespaces path: targetNamespaces - description: Use cert-manager to secure in-cluster communication between Cryostat diff --git a/internal/controllers/clustercryostat_controller_test.go b/internal/controllers/clustercryostat_controller_test.go index dcdc5a7b2..7e11a2884 100644 --- a/internal/controllers/clustercryostat_controller_test.go +++ b/internal/controllers/clustercryostat_controller_test.go @@ -121,6 +121,22 @@ var _ = Describe("ClusterCryostatController", func() { t.expectTargetNamespaces() }) }) + + Context("with no target namespaces", func() { + BeforeEach(func() { + t = c.commonBeforeEach() + t.TargetNamespaces = nil + t.objs = append(t.objs, t.NewCryostat().Object) + }) + + It("should reconcile successfully", func() { + t.reconcileCryostatFully() + }) + + It("should update the target namespaces in Status", func() { + t.expectTargetNamespaces() + }) + }) }) }) diff --git a/internal/controllers/model/instance.go b/internal/controllers/model/instance.go index 5230e8910..ec7440575 100644 --- a/internal/controllers/model/instance.go +++ b/internal/controllers/model/instance.go @@ -82,15 +82,10 @@ func FromCryostat(cr *operatorv1beta1.Cryostat) *CryostatInstance { // FromClusterCryostat creates a CryostatInstance from a ClusterCryostat CR func FromClusterCryostat(cr *operatorv1beta1.ClusterCryostat) *CryostatInstance { - // If target namespaces aren't explicitly listed, use the install namespace - targetNamespaces := cr.Spec.TargetNamespaces - if len(targetNamespaces) == 0 { - targetNamespaces = []string{cr.Spec.InstallNamespace} - } return &CryostatInstance{ Name: cr.Name, InstallNamespace: cr.Spec.InstallNamespace, - TargetNamespaces: targetNamespaces, + TargetNamespaces: cr.Spec.TargetNamespaces, TargetNamespaceStatus: &cr.Status.TargetNamespaces, Spec: &cr.Spec.CryostatSpec, From d3a7ed087a59105e1c23069a5ffc9818fe9f0377 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Thu, 23 Feb 2023 15:56:41 -0500 Subject: [PATCH 21/21] Fix list item typo --- api/v1beta1/clustercryostat_types.go | 2 +- api/v1beta1/zz_generated.deepcopy.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/v1beta1/clustercryostat_types.go b/api/v1beta1/clustercryostat_types.go index 7a50044c5..e2ae15aa3 100644 --- a/api/v1beta1/clustercryostat_types.go +++ b/api/v1beta1/clustercryostat_types.go @@ -94,7 +94,7 @@ type ClusterCryostat struct { type ClusterCryostatList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` - Items []Cryostat `json:"items"` + Items []ClusterCryostat `json:"items"` } func init() { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 4786f88da..53a48eac7 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -147,7 +147,7 @@ func (in *ClusterCryostatList) DeepCopyInto(out *ClusterCryostatList) { in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]Cryostat, len(*in)) + *out = make([]ClusterCryostat, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) }