diff --git a/PROJECT b/PROJECT index 27639981..c8125e63 100644 --- a/PROJECT +++ b/PROJECT @@ -182,4 +182,17 @@ resources: defaulting: true validation: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: roboscale.io + group: robot + kind: CodeEditor + path: github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2 + version: v1alpha2 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 version: "3" diff --git a/config/crd/bases/robot.roboscale.io_codeeditors.yaml b/config/crd/bases/robot.roboscale.io_codeeditors.yaml new file mode 100644 index 00000000..6eed9a65 --- /dev/null +++ b/config/crd/bases/robot.roboscale.io_codeeditors.yaml @@ -0,0 +1,2523 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: codeeditors.robot.roboscale.io +spec: + group: robot.roboscale.io + names: + kind: CodeEditor + listKind: CodeEditorList + plural: codeeditors + singular: codeeditor + scope: Namespaced + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: CodeEditor is the Schema for the codeeditors API + 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: Specification of the desired behavior of the CodeEditor. + properties: + container: + description: Configurational parameters for code editor container. + properties: + securityContext: + description: Security context of the code editor 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 + volumeMounts: + description: Mounted volumes of the code editor container. + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: Path within the container at which the volume + should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are + propagated from the host to container and the other way + around. When not set, MountPropagationNone is used. This + field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise + (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's + volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which + the container's volume should be mounted. Behaves similarly + to SubPath but environment variable references $(VAR_NAME) + are expanded using the container's environment. Defaults + to "" (volume's root). SubPathExpr and SubPath are mutually + exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + type: object + externalVolumes: + description: External volumes. + items: + description: Volume represents a named volume in a pod that may + be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: 'awsElasticBlockStore represents an AWS Disk resource + that is attached to a kubelet''s host machine and then exposed + to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + properties: + fsType: + description: 'fsType is the filesystem type of the volume + that you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'partition is the partition in the volume that + you want to mount. If omitted, the default is to mount + by volume name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property empty).' + format: int32 + type: integer + readOnly: + description: 'readOnly value true will force the readOnly + setting in VolumeMounts. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'volumeID is unique ID of the persistent disk + resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk mount on + the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: None, + Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in the + blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the blob + storage + type: string + fsType: + description: fsType is Filesystem type to mount. Must be + a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single blob + disk per storage account Managed: azure managed data + disk (only in managed availability set). defaults to shared' + type: string + readOnly: + description: readOnly Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service mount + on the host and bind mount to the pod. + properties: + readOnly: + description: readOnly defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that contains + Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on the host that + shares a pod's lifetime + properties: + monitors: + description: 'monitors is Required: Monitors is a collection + of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + items: + type: string + type: array + path: + description: 'path is Optional: Used as the mounted root, + rather than the full Ceph tree, default is /' + type: string + readOnly: + description: 'readOnly is Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'secretFile is Optional: SecretFile is the + path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'secretRef is Optional: SecretRef is reference + to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: 'user is optional: User is the rados user name, + default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + required: + - monitors + type: object + cinder: + description: 'cinder represents a cinder volume attached and + mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + properties: + fsType: + description: 'fsType is the filesystem type to mount. Must + be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to + be "ext4" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'readOnly defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'secretRef is optional: points to a secret + object containing parameters used to connect to OpenStack.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: 'volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should populate + this volume + properties: + defaultMode: + description: 'defaultMode is optional: mode bits used to + set permissions on created files by default. Must be an + octal value between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults to + 0644. Directories within the path are not affected by + this setting. This might be in conflict with other options + that affect the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + items: + description: items if unspecified, each key-value pair in + the Data field of the referenced ConfigMap will be projected + into the volume as a file whose name is the key and content + is the value. If specified, the listed keys will be projected + into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in + the ConfigMap, the volume setup will error unless it is + marked optional. Paths must be relative and may not contain + the '..' path or start with '..'. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used to + set permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. If not + specified, the volume defaultMode will be used. + This might be in conflict with other options that + affect the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: path is the relative path of the file + to map the key to. May not be an absolute path. + May not contain the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: optional specify whether the ConfigMap or its + keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents ephemeral + storage that is handled by certain external CSI drivers (Beta + feature). + properties: + driver: + description: driver is the name of the CSI driver that handles + this volume. Consult with your admin for the correct name + as registered in the cluster. + type: string + fsType: + description: fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated + CSI driver which will determine the default filesystem + to apply. + type: string + nodePublishSecretRef: + description: nodePublishSecretRef is a reference to the + secret object containing sensitive information to pass + to the CSI driver to complete the CSI NodePublishVolume + and NodeUnpublishVolume calls. This field is optional, + and may be empty if no secret is required. If the secret + object contains more than one secret, all secret references + are passed. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: readOnly specifies a read-only configuration + for the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: volumeAttributes stores driver-specific properties + that are passed to the CSI driver. Consult your driver's + documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about the pod + that should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created files + by default. Must be a Optional: mode bits used to set + permissions on created files by default. Must be an octal + value between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults to + 0644. Directories within the path are not affected by + this setting. This might be in conflict with other options + that affect the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + items: + description: Items is a list of downward API volume file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the pod: + only annotations, labels, name and namespace are + supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: 'Optional: mode bits used to set permissions + on this file, must be an octal value between 0000 + and 0777 or a decimal value between 0 and 511. YAML + accepts both octal and decimal values, JSON requires + decimal values for mode bits. If not specified, + the volume defaultMode will be used. This might + be in conflict with other options that affect the + file mode, like fsGroup, and the result can be other + mode bits set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative path + name of the file to be created. Must not be absolute + or contain the ''..'' path. Must be utf-8 encoded. + The first item of the relative path must not start + with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + description: 'emptyDir represents a temporary directory that + shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + properties: + medium: + description: 'medium represents what type of storage medium + should back this directory. The default is "" which means + to use the node''s default medium. Must be an empty string + (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: 'sizeLimit is the total amount of local storage + required for this EmptyDir volume. The size limit is also + applicable for memory medium. The maximum usage on memory + medium EmptyDir would be the minimum value between the + SizeLimit specified here and the sum of memory limits + of all containers in a pod. The default is nil which means + that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: "ephemeral represents a volume that is handled + by a cluster storage driver. The volume's lifecycle is tied + to the pod that defines it - it will be created before the + pod starts, and deleted when the pod is removed. \n Use this + if: a) the volume is only needed while the pod runs, b) features + of normal volumes like restoring from snapshot or capacity + tracking are needed, c) the storage driver is specified through + a storage class, and d) the storage driver supports dynamic + volume provisioning through a PersistentVolumeClaim (see EphemeralVolumeSource + for more information on the connection between this volume + type and PersistentVolumeClaim). \n Use PersistentVolumeClaim + or one of the vendor-specific APIs for volumes that persist + for longer than the lifecycle of an individual pod. \n Use + CSI for light-weight local ephemeral volumes if the CSI driver + is meant to be used that way - see the documentation of the + driver for more information. \n A pod can use both types of + ephemeral volumes and persistent volumes at the same time." + properties: + volumeClaimTemplate: + description: "Will be used to create a stand-alone PVC to + provision the volume. The pod in which this EphemeralVolumeSource + is embedded will be the owner of the PVC, i.e. the PVC + will be deleted together with the pod. The name of the + PVC will be `-` where `` is the name from the `PodSpec.Volumes` array entry. + Pod validation will reject the pod if the concatenated + name is not valid for a PVC (for example, too long). \n + An existing PVC with that name that is not owned by the + pod will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC + is meant to be used by the pod, the PVC has to updated + with an owner reference to the pod once the pod exists. + Normally this should not be necessary, but it may be useful + when manually reconstructing a broken cluster. \n This + field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. \n Required, must + not be nil." + properties: + metadata: + description: May contain labels and annotations that + will be copied into the PVC when creating it. No other + fields are allowed and will be rejected during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. + The entire content is copied unchanged into the PVC + that gets created from this template. The same fields + as in a PersistentVolumeClaim are also valid here. + 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. When the AnyVolumeDataSource feature + gate is enabled, dataSource contents will be copied + to dataSourceRef, and dataSourceRef contents will + be copied to dataSource when dataSourceRef.namespace + is not specified. If the namespace is specified, + then dataSourceRef will not be copied to dataSource.' + 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 + x-kubernetes-map-type: atomic + 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 + 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, when namespace isn''t + specified in dataSourceRef, 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. When namespace is specified + in dataSourceRef, dataSource isn''t set to the + same value and must be empty. There are three + 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. + * While dataSource only allows local objects, + dataSourceRef allows objects in any namespaces. + (Beta) Using this field requires the AnyVolumeDataSource + feature gate to be enabled. (Alpha) Using the + namespace field of dataSourceRef requires the + CrossNamespaceVolumeDataSource 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 + namespace: + description: Namespace is the namespace of resource + being referenced Note that when a namespace + is specified, a gateway.networking.k8s.io/ReferenceGrant + object is required in the referent namespace + to allow that namespace's owner to accept + the reference. See the ReferenceGrant documentation + for details. (Alpha) This field requires the + CrossNamespaceVolumeDataSource feature gate + to be enabled. + 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: + claims: + description: "Claims lists the names of resources, + defined in spec.resourceClaims, that are used + by this container. \n This is an alpha field + and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable." + items: + description: ResourceClaim references one + entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name + of one entry in pod.spec.resourceClaims + of the Pod where this field is used. + It makes that resource available inside + a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + 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 + x-kubernetes-map-type: atomic + 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 + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource that is + attached to a kubelet's host machine and then exposed to the + pod. + properties: + fsType: + description: 'fsType is the filesystem type to mount. Must + be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. TODO: how do we prevent errors in the + filesystem from compromising the machine' + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: 'readOnly is Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide + names (WWNs)' + items: + type: string + type: array + wwids: + description: 'wwids Optional: FC volume world wide identifiers + (wwids) Either wwids or combination of targetWWNs and + lun must be set, but not both simultaneously.' + items: + type: string + type: array + type: object + flexVolume: + description: flexVolume represents a generic volume resource + that is provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver to use for + this volume. + type: string + fsType: + description: fsType is the filesystem type to mount. Must + be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends + on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds extra + command options if any.' + type: object + readOnly: + description: 'readOnly is Optional: defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + secretRef: + description: 'secretRef is Optional: secretRef is reference + to the secret object containing sensitive information + to pass to the plugin scripts. This may be empty if no + secret object is specified. If the secret object contains + more than one secret, all secrets are passed to the plugin + scripts.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached to + a kubelet's host machine. This depends on the Flocker control + service being running + properties: + datasetName: + description: datasetName is Name of the dataset stored as + metadata -> name on the dataset for Flocker should be + considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. This + is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: 'gcePersistentDisk represents a GCE Disk resource + that is attached to a kubelet''s host machine and then exposed + to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + properties: + fsType: + description: 'fsType is filesystem type of the volume that + you want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'partition is the partition in the volume that + you want to mount. If omitted, the default is to mount + by volume name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + format: int32 + type: integer + pdName: + description: 'pdName is unique name of the PD resource in + GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'readOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + required: + - pdName + type: object + gitRepo: + description: 'gitRepo represents a git repository at a particular + revision. DEPRECATED: GitRepo is deprecated. To provision + a container with a git repo, mount an EmptyDir into an InitContainer + that clones the repo using git, then mount the EmptyDir into + the Pod''s container.' + properties: + directory: + description: directory is the target directory name. Must + not contain or start with '..'. If '.' is supplied, the + volume directory will be the git repository. Otherwise, + if specified, the volume will contain the git repository + in the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified + revision. + type: string + required: + - repository + type: object + glusterfs: + description: 'glusterfs represents a Glusterfs mount on the + host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + properties: + endpoints: + description: 'endpoints is the endpoint name that details + Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'path is the Glusterfs volume path. More info: + https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'readOnly here will force the Glusterfs volume + to be mounted with read-only permissions. Defaults to + false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: 'hostPath represents a pre-existing file or directory + on the host machine that is directly exposed to the container. + This is generally used for system agents or other privileged + things that are allowed to see the host machine. Most containers + will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- TODO(jonesdl) We need to restrict who can use host directory + mounts and who can/can not mount host directories as read/write.' + properties: + path: + description: 'path of the directory on the host. If the + path is a symlink, it will follow the link to the real + path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'type for HostPath Volume Defaults to "" More + info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + required: + - path + type: object + iscsi: + description: 'iscsi represents an ISCSI Disk resource that is + attached to a kubelet''s host machine and then exposed to + the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support iSCSI + Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support iSCSI + Session CHAP authentication + type: boolean + fsType: + description: 'fsType is the filesystem type of the volume + that you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + initiatorName: + description: initiatorName is the custom iSCSI Initiator + Name. If initiatorName is specified with iscsiInterface + simultaneously, new iSCSI interface : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iscsiInterface is the interface Name that uses + an iSCSI transport. Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: portals is the iSCSI Target Portal List. The + portal is either an IP or ip_addr:port if the port is + other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: readOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI target + and initiator authentication + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: targetPortal is iSCSI Target Portal. The Portal + is either an IP or ip_addr:port if the port is other than + default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: 'name of the volume. Must be a DNS_LABEL and unique + within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'nfs represents an NFS mount on the host that shares + a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + properties: + path: + description: 'path that is exported by the NFS server. More + info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'readOnly here will force the NFS export to + be mounted with read-only permissions. Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'server is the hostname or IP address of the + NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: 'persistentVolumeClaimVolumeSource represents a + reference to a PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + properties: + claimName: + description: 'claimName is the name of a PersistentVolumeClaim + in the same namespace as the pod using this volume. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: readOnly Will force the ReadOnly setting in + VolumeMounts. Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host machine + properties: + fsType: + description: fsType is the filesystem type to mount. Must + be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx volume attached + and mounted on kubelets host machine + properties: + fsType: + description: fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating + system. Ex. "ext4", "xfs". Implicitly inferred to be "ext4" + if unspecified. + type: string + readOnly: + description: readOnly defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: defaultMode are the mode bits used to set permissions + on created files by default. Must be an octal value between + 0000 and 0777 or a decimal value between 0 and 511. YAML + accepts both octal and decimal values, JSON requires decimal + values for mode bits. Directories within the path are + not affected by this setting. This might be in conflict + with other options that affect the file mode, like fsGroup, + and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: sources is the list of volume projections + items: + description: Projection that may be projected along with + other supported volume types + properties: + configMap: + description: configMap information about the configMap + data to project + properties: + items: + description: items if unspecified, each key-value + pair in the Data field of the referenced ConfigMap + will be projected into the volume as a file + whose name is the key and content is the value. + If specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified which + is not present in the ConfigMap, the volume + setup will error unless it is marked optional. + Paths must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits + used to set permissions on this file. + Must be an octal value between 0000 and + 0777 or a decimal value between 0 and + 511. YAML accepts both octal and decimal + values, JSON requires decimal values for + mode bits. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: path is the relative path of + the file to map the key to. May not be + an absolute path. May not contain the + path element '..'. May not start with + the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the downwardAPI + data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: 'Optional: mode bits used to + set permissions on this file, must be + an octal value between 0000 and 0777 or + a decimal value between 0 and 511. YAML + accepts both octal and decimal values, + JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict + with other options that affect the file + mode, like fsGroup, and the result can + be other mode bits set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must + not be absolute or contain the ''..'' + path. Must be utf-8 encoded. The first + item of the relative path must not start + with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the + container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu + and requests.memory) are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + description: secret information about the secret data + to project + properties: + items: + description: items if unspecified, each key-value + pair in the Data field of the referenced Secret + will be projected into the volume as a file + whose name is the key and content is the value. + If specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified which + is not present in the Secret, the volume setup + will error unless it is marked optional. Paths + must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits + used to set permissions on this file. + Must be an octal value between 0000 and + 0777 or a decimal value between 0 and + 511. YAML accepts both octal and decimal + values, JSON requires decimal values for + mode bits. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: path is the relative path of + the file to map the key to. May not be + an absolute path. May not contain the + path element '..'. May not start with + the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: optional field specify whether the + Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information about + the serviceAccountToken data to project + properties: + audience: + description: audience is the intended audience + of the token. A recipient of a token must identify + itself with an identifier specified in the audience + of the token, and otherwise should reject the + token. The audience defaults to the identifier + of the apiserver. + type: string + expirationSeconds: + description: expirationSeconds is the requested + duration of validity of the service account + token. As the token approaches expiration, the + kubelet volume plugin will proactively rotate + the service account token. The kubelet will + start trying to rotate the token if the token + is older than 80 percent of its time to live + or if the token is older than 24 hours.Defaults + to 1 hour and must be at least 10 minutes. + format: int64 + type: integer + path: + description: path is the path relative to the + mount point of the file to project the token + into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: group to map volume access to Default is no + group + type: string + readOnly: + description: readOnly here will force the Quobyte volume + to be mounted with read-only permissions. Defaults to + false. + type: boolean + registry: + description: registry represents a single or multiple Quobyte + Registry services specified as a string as host:port pair + (multiple entries are separated with commas) which acts + as the central registry for volumes + type: string + tenant: + description: tenant owning the given Quobyte volume in the + Backend Used with dynamically provisioned Quobyte volumes, + value is set by the plugin + type: string + user: + description: user to map volume access to Defaults to serivceaccount + user + type: string + volume: + description: volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: 'rbd represents a Rados Block Device mount on the + host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md' + properties: + fsType: + description: 'fsType is the filesystem type of the volume + that you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + image: + description: 'image is the rados image name. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + items: + type: string + type: array + pool: + description: 'pool is the rados pool name. Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'readOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'secretRef is name of the authentication secret + for RBDUser. If provided overrides keyring. Default is + nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: 'user is the rados user name. Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: fsType is the filesystem type to mount. Must + be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO + API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO + Protection Domain for the configured storage. + type: string + readOnly: + description: readOnly Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: secretRef references to the secret for ScaleIO + user and other sensitive information. If this is not provided, + Login operation will fail. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: storageMode indicates whether the storage for + a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool associated + with the protection domain. + type: string + system: + description: system is the name of the storage system as + configured in ScaleIO. + type: string + volumeName: + description: volumeName is the name of a volume already + created in the ScaleIO system that is associated with + this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: 'secret represents a secret that should populate + this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + properties: + defaultMode: + description: 'defaultMode is Optional: mode bits used to + set permissions on created files by default. Must be an + octal value between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults to + 0644. Directories within the path are not affected by + this setting. This might be in conflict with other options + that affect the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + items: + description: items If unspecified, each key-value pair in + the Data field of the referenced Secret will be projected + into the volume as a file whose name is the key and content + is the value. If specified, the listed keys will be projected + into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in + the Secret, the volume setup will error unless it is marked + optional. Paths must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used to + set permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. If not + specified, the volume defaultMode will be used. + This might be in conflict with other options that + affect the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: path is the relative path of the file + to map the key to. May not be an absolute path. + May not contain the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the Secret or + its keys must be defined + type: boolean + secretName: + description: 'secretName is the name of the secret in the + pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: fsType is the filesystem type to mount. Must + be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. + type: string + readOnly: + description: readOnly defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: secretRef specifies the secret to use for obtaining + the StorageOS API credentials. If not specified, default + values will be attempted. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: volumeName is the human-readable name of the + StorageOS volume. Volume names are only unique within + a namespace. + type: string + volumeNamespace: + description: volumeNamespace specifies the scope of the + volume within StorageOS. If no namespace is specified + then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS + for tighter integration. Set VolumeName to any name to + override the default behaviour. Set to "default" if you + are not using namespaces within StorageOS. Namespaces + that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: fsType is filesystem type to mount. Must be + a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based + Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy Based + Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies vSphere + volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + port: + default: 9000 + description: Port that code editor will use inside the container. + format: int32 + type: integer + remote: + description: If `true`, code editor will be consumed remotely. + type: boolean + root: + description: If `true`, code editor will be consumed as `root` user. + type: boolean + volumeClaimTemplates: + description: Volume templates for ROS 2 workload. For each volume + template, operator will create a PersistentVolumeClaim that can + be mounted to the ROS 2 workload. + items: + description: PersistentVolumeClaimTemplate is used to produce PersistentVolumeClaim + objects as part of an EphemeralVolumeSource. + properties: + metadata: + description: May contain labels and annotations that will be + copied into the PVC when creating it. No other fields are + allowed and will be rejected during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. + The entire content is copied unchanged into the PVC that gets + created from this template. The same fields as in a PersistentVolumeClaim + are also valid here. + 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. When the AnyVolumeDataSource + feature gate is enabled, dataSource contents will be copied + to dataSourceRef, and dataSourceRef contents will be copied + to dataSource when dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef will + not be copied to dataSource.' + 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 + x-kubernetes-map-type: atomic + 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 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, when + namespace isn''t specified in dataSourceRef, 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. When namespace is specified in dataSourceRef, + dataSource isn''t set to the same value and must be empty. + There are three 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. * While dataSource + only allows local objects, dataSourceRef allows objects + in any namespaces. (Beta) Using this field requires the + AnyVolumeDataSource feature gate to be enabled. (Alpha) + Using the namespace field of dataSourceRef requires the + CrossNamespaceVolumeDataSource 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 + namespace: + description: Namespace is the namespace of resource + being referenced Note that when a namespace is specified, + a gateway.networking.k8s.io/ReferenceGrant object + is required in the referent namespace to allow that + namespace's owner to accept the reference. See the + ReferenceGrant documentation for details. (Alpha) + This field requires the CrossNamespaceVolumeDataSource + feature gate to be enabled. + 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: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. \n This field + is immutable." + items: + description: ResourceClaim references one entry in + PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry + in pod.spec.resourceClaims of the Pod where + this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + 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 + x-kubernetes-map-type: atomic + 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 + required: + - spec + type: object + type: array + required: + - port + type: object + status: + description: Most recently observed status of the CodeEditor. + properties: + deploymentStatus: + description: Status of code editor deployment. + properties: + containerStatuses: + description: Container statuses. + items: + description: ContainerStatus contains details for the current + status of this container. + properties: + containerID: + description: Container's ID in the format '://'. + type: string + image: + description: 'The image the container is running. More info: + https://kubernetes.io/docs/concepts/containers/images.' + type: string + imageID: + description: ImageID of the container's image. + type: string + lastState: + description: Details about the container's last termination + condition. + properties: + running: + description: Details about a running container + properties: + startedAt: + description: Time at which the container was last + (re-)started + format: date-time + type: string + type: object + terminated: + description: Details about a terminated container + properties: + containerID: + description: Container's ID in the format '://' + type: string + exitCode: + description: Exit status from the last termination + of the container + format: int32 + type: integer + finishedAt: + description: Time at which the container last terminated + format: date-time + type: string + message: + description: Message regarding the last termination + of the container + type: string + reason: + description: (brief) reason from the last termination + of the container + type: string + signal: + description: Signal from the last termination of + the container + format: int32 + type: integer + startedAt: + description: Time at which previous execution of + the container started + format: date-time + type: string + required: + - exitCode + type: object + waiting: + description: Details about a waiting container + properties: + message: + description: Message regarding why the container + is not yet running. + type: string + reason: + description: (brief) reason the container is not + yet running. + type: string + type: object + type: object + name: + description: This must be a DNS_LABEL. Each container in + a pod must have a unique name. Cannot be updated. + type: string + ready: + description: Specifies whether the container has passed + its readiness probe. + type: boolean + restartCount: + description: The number of times the container has been + restarted. + format: int32 + type: integer + started: + description: Specifies whether the container has passed + its startup probe. Initialized as false, becomes true + after startupProbe is considered successful. Resets to + false when the container is restarted, or if kubelet loses + state temporarily. Is always true when no startupProbe + is defined. + type: boolean + state: + description: Details about the container's current condition. + properties: + running: + description: Details about a running container + properties: + startedAt: + description: Time at which the container was last + (re-)started + format: date-time + type: string + type: object + terminated: + description: Details about a terminated container + properties: + containerID: + description: Container's ID in the format '://' + type: string + exitCode: + description: Exit status from the last termination + of the container + format: int32 + type: integer + finishedAt: + description: Time at which the container last terminated + format: date-time + type: string + message: + description: Message regarding the last termination + of the container + type: string + reason: + description: (brief) reason from the last termination + of the container + type: string + signal: + description: Signal from the last termination of + the container + format: int32 + type: integer + startedAt: + description: Time at which previous execution of + the container started + format: date-time + type: string + required: + - exitCode + type: object + waiting: + description: Details about a waiting container + properties: + message: + description: Message regarding why the container + is not yet running. + type: string + reason: + description: (brief) reason the container is not + yet running. + type: string + type: object + type: object + required: + - image + - imageID + - name + - ready + - restartCount + type: object + type: array + resource: + description: Generic status for any owned resource. + properties: + created: + description: Shows if the owned resource is created. + type: boolean + phase: + description: Phase of the owned resource. + type: string + reference: + description: Reference to the owned resource. + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead + of an entire object, this string should contain a valid + JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container + within a pod, this would take on a value like: "spec.containers{name}" + (where "name" refers to the name of the container that + triggered the event) or if no container name is specified + "spec.containers[2]" (container with index 2 in this + pod). This syntax is chosen only to have some well-defined + way of referencing a part of an object. TODO: this design + is not final and this field is subject to change in + the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference + is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + x-kubernetes-map-type: atomic + required: + - created + type: object + status: + description: Status of the Deployment. + properties: + availableReplicas: + description: Total number of available pods (ready for at + least minReadySeconds) targeted by this deployment. + format: int32 + type: integer + collisionCount: + description: Count of hash collisions for the Deployment. + The Deployment controller uses this field as a collision + avoidance mechanism when it needs to create the name for + the newest ReplicaSet. + format: int32 + type: integer + conditions: + description: Represents the latest available observations + of a deployment's current state. + items: + description: DeploymentCondition describes the state of + a deployment at a certain point. + properties: + lastTransitionTime: + description: Last time the condition transitioned from + one status to another. + format: date-time + type: string + lastUpdateTime: + description: The last time this condition was updated. + format: date-time + type: string + message: + description: A human readable message indicating details + about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, + Unknown. + type: string + type: + description: Type of deployment condition. + type: string + required: + - status + - type + type: object + type: array + observedGeneration: + description: The generation observed by the deployment controller. + format: int64 + type: integer + readyReplicas: + description: readyReplicas is the number of pods targeted + by this Deployment with a Ready Condition. + format: int32 + type: integer + replicas: + description: Total number of non-terminated pods targeted + by this deployment (their labels match the selector). + format: int32 + type: integer + unavailableReplicas: + description: Total number of unavailable pods targeted by + this deployment. This is the total number of pods that are + still required for the deployment to have 100% available + capacity. They may either be pods that are running but not + yet available or pods that still have not been created. + format: int32 + type: integer + updatedReplicas: + description: Total number of non-terminated pods targeted + by this deployment that have the desired template spec. + format: int32 + type: integer + type: object + type: object + externalVolumeStatuses: + description: Statuses of external volumes. + items: + properties: + exists: + description: Indicates if the volume exists. + type: boolean + name: + description: Name of the external volume. + type: string + type: object + type: array + phase: + description: Phase of CodeEditor. It sums the general status of code + editor. + type: string + pvcStatuses: + description: Statuses of owned PersistentVolumeClaims. + items: + properties: + resource: + description: Generic status for any owned resource. + properties: + created: + description: Shows if the owned resource is created. + type: boolean + phase: + description: Phase of the owned resource. + type: string + reference: + description: Reference to the owned resource. + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead + of an entire object, this string should contain a + valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container + within a pod, this would take on a value like: "spec.containers{name}" + (where "name" refers to the name of the container + that triggered the event) or if no container name + is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to + have some well-defined way of referencing a part of + an object. TODO: this design is not final and this + field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this + reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + x-kubernetes-map-type: atomic + required: + - created + type: object + status: + description: Status of the ROS2Bridge instance. + properties: + accessModes: + description: 'accessModes contains the actual access modes + the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + allocatedResources: + 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: allocatedResources is the storage resource + within AllocatedResources tracks the capacity allocated + to a PVC. It may be larger than the actual capacity when + a volume expansion operation is requested. For storage + quota, the larger value from allocatedResources and PVC.spec.resources + is used. If allocatedResources is not set, PVC.spec.resources + alone is used for quota calculation. If a volume expansion + capacity request is lowered, allocatedResources is only + lowered if there are no expansion operations in progress + and if the actual volume capacity is equal or lower than + the requested capacity. This is an alpha field and requires + enabling RecoverVolumeExpansionFailure feature. + type: object + capacity: + 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: capacity represents the actual resources of + the underlying volume. + type: object + conditions: + description: conditions is the current Condition of persistent + volume claim. If underlying persistent volume is being + resized then the Condition will be set to 'ResizeStarted'. + items: + description: PersistentVolumeClaimCondition contails details + about state of pvc + properties: + lastProbeTime: + description: lastProbeTime is the time we probed the + condition. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime is the time the condition + transitioned from one status to another. + format: date-time + type: string + message: + description: message is the human-readable message + indicating details about last transition. + type: string + reason: + description: reason is a unique, this should be a + short, machine understandable string that gives + the reason for condition's last transition. If it + reports "ResizeStarted" that means the underlying + persistent volume is being resized. + type: string + status: + type: string + type: + description: PersistentVolumeClaimConditionType is + a valid value of PersistentVolumeClaimCondition.Type + type: string + required: + - status + - type + type: object + type: array + phase: + description: phase represents the current phase of PersistentVolumeClaim. + type: string + resizeStatus: + description: resizeStatus stores status of resize operation. + ResizeStatus is not set by default but when expansion + is complete resizeStatus is set to empty string by resize + controller or kubelet. This is an alpha field and requires + enabling RecoverVolumeExpansionFailure feature. + type: string + type: object + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/robot.roboscale.io_ros2workloads.yaml b/config/crd/bases/robot.roboscale.io_ros2workloads.yaml index 9cb4736e..44b0d023 100644 --- a/config/crd/bases/robot.roboscale.io_ros2workloads.yaml +++ b/config/crd/bases/robot.roboscale.io_ros2workloads.yaml @@ -2622,7 +2622,7 @@ spec: - created type: object status: - description: Status of the ROS2Bridge instance. + description: Status of the StatefulSet. properties: availableReplicas: description: Total number of available pods (ready for at diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 5c4d786e..c05b8d32 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -20,6 +20,7 @@ resources: - bases/robot.roboscale.io_notebooks.yaml - bases/robot.roboscale.io_ros2workloads.yaml - bases/robot.roboscale.io_ros2bridges.yaml +- bases/robot.roboscale.io_codeeditors.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -41,6 +42,7 @@ patchesStrategicMerge: #- patches/webhook_in_notebooks.yaml #- patches/webhook_in_ros2workloads.yaml #- patches/webhook_in_ros2bridges.yaml +#- patches/webhook_in_codeeditors.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -61,6 +63,7 @@ patchesStrategicMerge: #- patches/cainjection_in_notebooks.yaml #- patches/cainjection_in_ros2workloads.yaml #- patches/cainjection_in_ros2bridges.yaml +#- patches/cainjection_in_codeeditors.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_codeeditors.yaml b/config/crd/patches/cainjection_in_codeeditors.yaml new file mode 100644 index 00000000..c2e7cd2e --- /dev/null +++ b/config/crd/patches/cainjection_in_codeeditors.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: codeeditors.robot.roboscale.io diff --git a/config/crd/patches/webhook_in_codeeditors.yaml b/config/crd/patches/webhook_in_codeeditors.yaml new file mode 100644 index 00000000..b99d38bb --- /dev/null +++ b/config/crd/patches/webhook_in_codeeditors.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: codeeditors.robot.roboscale.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/codeeditor_editor_role.yaml b/config/rbac/codeeditor_editor_role.yaml new file mode 100644 index 00000000..446f20cc --- /dev/null +++ b/config/rbac/codeeditor_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit codeeditors. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: codeeditor-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: robot-operator + app.kubernetes.io/part-of: robot-operator + app.kubernetes.io/managed-by: kustomize + name: codeeditor-editor-role +rules: +- apiGroups: + - robot.roboscale.io + resources: + - codeeditors + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - robot.roboscale.io + resources: + - codeeditors/status + verbs: + - get diff --git a/config/rbac/codeeditor_viewer_role.yaml b/config/rbac/codeeditor_viewer_role.yaml new file mode 100644 index 00000000..a413b8e0 --- /dev/null +++ b/config/rbac/codeeditor_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view codeeditors. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: codeeditor-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: robot-operator + app.kubernetes.io/part-of: robot-operator + app.kubernetes.io/managed-by: kustomize + name: codeeditor-viewer-role +rules: +- apiGroups: + - robot.roboscale.io + resources: + - codeeditors + verbs: + - get + - list + - watch +- apiGroups: + - robot.roboscale.io + resources: + - codeeditors/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 04ca76b4..98ceb84a 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -5,6 +5,18 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - apps resources: @@ -187,6 +199,32 @@ rules: - get - patch - update +- apiGroups: + - robot.roboscale.io + resources: + - codeeditors + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - robot.roboscale.io + resources: + - codeeditors/finalizers + verbs: + - update +- apiGroups: + - robot.roboscale.io + resources: + - codeeditors/status + verbs: + - get + - patch + - update - apiGroups: - robot.roboscale.io resources: diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index f61ac29a..840b0b40 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -225,6 +225,26 @@ webhooks: resources: - ros2bridges sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-robot-roboscale-io-v1alpha2-codeeditor + failurePolicy: Fail + name: mcodeeditor.kb.io + rules: + - apiGroups: + - robot.roboscale.io + apiVersions: + - v1alpha2 + operations: + - CREATE + - UPDATE + resources: + - codeeditors + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration @@ -452,3 +472,23 @@ webhooks: resources: - ros2bridges sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-robot-roboscale-io-v1alpha2-codeeditor + failurePolicy: Fail + name: vcodeeditor.kb.io + rules: + - apiGroups: + - robot.roboscale.io + apiVersions: + - v1alpha2 + operations: + - CREATE + - UPDATE + resources: + - codeeditors + sideEffects: None diff --git a/internal/configure/v1alpha2/linux_user.go b/internal/configure/v1alpha2/linux_user.go new file mode 100644 index 00000000..f935f0c9 --- /dev/null +++ b/internal/configure/v1alpha2/linux_user.go @@ -0,0 +1,20 @@ +package configure + +import ( + corev1 "k8s.io/api/core/v1" +) + +func (cfg *PodSpecConfigInjector) InjectLinuxUserAndGroup(podSpec *corev1.PodSpec) { + + var user int64 = 1000 + var group int64 = 3000 + + for key, cont := range podSpec.Containers { + cont.SecurityContext = &corev1.SecurityContext{ + RunAsUser: &user, + RunAsGroup: &group, + Privileged: cont.SecurityContext.Privileged, + } + podSpec.Containers[key] = cont + } +} diff --git a/internal/configure/v1alpha2/runtime_class.go b/internal/configure/v1alpha2/runtime_class.go index deddeab6..1a8151e2 100644 --- a/internal/configure/v1alpha2/runtime_class.go +++ b/internal/configure/v1alpha2/runtime_class.go @@ -3,12 +3,12 @@ package configure import ( "github.com/robolaunch/robot-operator/internal/label" "github.com/robolaunch/robot-operator/internal/node" - "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func (cfg *PodSpecConfigInjector) InjectRuntimeClass(podSpec *corev1.PodSpec, ros2Workload v1alpha2.ROS2Workload, currentNode corev1.Node) { - if label.GetInstanceType(&ros2Workload) == label.InstanceTypeCloudInstance && node.IsK3s(currentNode) { +func (cfg *PodSpecConfigInjector) InjectRuntimeClass(podSpec *corev1.PodSpec, obj metav1.Object, currentNode corev1.Node) { + if label.GetInstanceType(obj) == label.InstanceTypeCloudInstance && node.IsK3s(currentNode) { nvidiaRuntimeClass := "nvidia" podSpec.RuntimeClassName = &nvidiaRuntimeClass } diff --git a/internal/configure/v1alpha2/volume.go b/internal/configure/v1alpha2/volume.go index 6851351d..cabad81a 100644 --- a/internal/configure/v1alpha2/volume.go +++ b/internal/configure/v1alpha2/volume.go @@ -5,12 +5,16 @@ import ( corev1 "k8s.io/api/core/v1" ) -func (cfg *PodSpecConfigInjector) InjectVolumeConfiguration(podSpec *corev1.PodSpec, ros2Workload robotv1alpha2.ROS2Workload) { - cfg.injectOwnedPVCVolumes(podSpec, ros2Workload) +func (cfg *PodSpecConfigInjector) InjectVolumeConfiguration(podSpec *corev1.PodSpec, pvcStatuses []robotv1alpha2.OwnedPVCStatus) { + cfg.injectOwnedPVCVolumes(podSpec, pvcStatuses) } -func (cfg *PodSpecConfigInjector) injectOwnedPVCVolumes(podSpec *corev1.PodSpec, ros2Workload robotv1alpha2.ROS2Workload) { - for _, pvcStatus := range ros2Workload.Status.PVCStatuses { +func (cfg *PodSpecConfigInjector) InjectExternalVolumeConfiguration(podSpec *corev1.PodSpec, evStatuses []robotv1alpha2.ExternalVolumeStatus) { + cfg.injectExternalPVCVolumes(podSpec, evStatuses) +} + +func (cfg *PodSpecConfigInjector) injectOwnedPVCVolumes(podSpec *corev1.PodSpec, pvcStatuses []robotv1alpha2.OwnedPVCStatus) { + for _, pvcStatus := range pvcStatuses { podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{ Name: pvcStatus.Resource.Reference.Name, VolumeSource: corev1.VolumeSource{ @@ -21,3 +25,16 @@ func (cfg *PodSpecConfigInjector) injectOwnedPVCVolumes(podSpec *corev1.PodSpec, }) } } + +func (cfg *PodSpecConfigInjector) injectExternalPVCVolumes(podSpec *corev1.PodSpec, evStatuses []robotv1alpha2.ExternalVolumeStatus) { + for _, evStatus := range evStatuses { + podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{ + Name: evStatus.Name, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: evStatus.Name, + }, + }, + }) + } +} diff --git a/internal/resources/v1alpha2/code_editor.go b/internal/resources/v1alpha2/code_editor.go new file mode 100644 index 00000000..06756a53 --- /dev/null +++ b/internal/resources/v1alpha2/code_editor.go @@ -0,0 +1,96 @@ +package v1alpha2_resources + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/robolaunch/robot-operator/internal" + configure "github.com/robolaunch/robot-operator/internal/configure/v1alpha2" + robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2" +) + +const ( + CODE_EDITOR_PORT_NAME = "code-server" +) + +func GetCodeEditorPersistentVolumeClaim(codeEditor *robotv1alpha2.CodeEditor, pvcNamespacedName *types.NamespacedName, key int) *corev1.PersistentVolumeClaim { + + pvc := corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: pvcNamespacedName.Name, + Namespace: pvcNamespacedName.Namespace, + Labels: codeEditor.Labels, + }, + Spec: codeEditor.Spec.VolumeClaimTemplates[key].Spec, + } + + return &pvc + +} + +func GetCodeEditorDeployment(codeEditor *robotv1alpha2.CodeEditor, deploymentNamespacedName *types.NamespacedName, node corev1.Node) *appsv1.Deployment { + + cfg := configure.PodSpecConfigInjector{} + + podSpec := corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "code-editor", + Image: "docker.io/robolaunchio/code-editor:4.22.0-0.2.6-alpha.19", + Command: []string{"/bin/bash", "-c", "sleep infinity"}, + Ports: []corev1.ContainerPort{ + { + Name: CODE_EDITOR_PORT_NAME, + ContainerPort: codeEditor.Spec.Port, + Protocol: corev1.ProtocolTCP, + }, + { + Name: internal.FILE_BROWSER_PORT_NAME, + ContainerPort: internal.FILE_BROWSER_PORT, + Protocol: corev1.ProtocolTCP, + }, + }, + SecurityContext: &codeEditor.Spec.Container.SecurityContext, + VolumeMounts: codeEditor.Spec.Container.VolumeMounts, + }, + }, + } + + cfg.InjectImagePullPolicy(&podSpec) + cfg.SchedulePod(&podSpec, codeEditor) + cfg.InjectTimezone(&podSpec, node) + cfg.InjectRuntimeClass(&podSpec, codeEditor, node) + cfg.InjectVolumeConfiguration(&podSpec, codeEditor.Status.PVCStatuses) + cfg.InjectExternalVolumeConfiguration(&podSpec, codeEditor.Status.ExternalVolumeStatuses) + + if !codeEditor.Spec.Root { + cfg.InjectLinuxUserAndGroup(&podSpec) + } + + deployment := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: deploymentNamespacedName.Name, + Namespace: deploymentNamespacedName.Namespace, + Labels: codeEditor.Labels, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + internal.CODE_EDITOR_CONTAINER_SELECTOR_LABEL_KEY: "code-editor", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + internal.CODE_EDITOR_CONTAINER_SELECTOR_LABEL_KEY: "code-editor", + }, + }, + Spec: podSpec, + }, + }, + } + + return &deployment +} diff --git a/internal/resources/v1alpha2/ros2_bridge.go b/internal/resources/v1alpha2/ros2_bridge.go index 4a9f2614..1da4807d 100644 --- a/internal/resources/v1alpha2/ros2_bridge.go +++ b/internal/resources/v1alpha2/ros2_bridge.go @@ -18,10 +18,8 @@ import ( ) const ( - ROS_BRIDGE_PORT_NAME = "bridge-server" - ROS_BRIDGE_PORT = 9090 - ROS2_BRIDGE_PORT_NAME = "bridge-server-2" - ROS2_BRIDGE_PORT = 9091 + ROS2_BRIDGE_PORT_NAME = "bridge-server" + ROS2_BRIDGE_PORT = 9090 ) func getROSBridgeSelector(rosbridge robotv1alpha2.ROS2Bridge) map[string]string { @@ -79,14 +77,6 @@ func GetROS2BridgeService(rosbridge *robotv1alpha2.ROS2Bridge, svcNamespacedName }, Spec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ - { - Name: ROS_BRIDGE_PORT_NAME, - Port: ROS_BRIDGE_PORT, - Protocol: corev1.ProtocolTCP, - TargetPort: intstr.IntOrString{ - IntVal: ROS_BRIDGE_PORT, - }, - }, { Name: ROS2_BRIDGE_PORT_NAME, Port: ROS2_BRIDGE_PORT, diff --git a/internal/resources/v1alpha2/ros2_workload.go b/internal/resources/v1alpha2/ros2_workload.go index d987c9e9..9cdaa841 100644 --- a/internal/resources/v1alpha2/ros2_workload.go +++ b/internal/resources/v1alpha2/ros2_workload.go @@ -12,7 +12,7 @@ import ( robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2" ) -func GetDiscoveryServer(ros2Workload *robotv1alpha2.ROS2Workload, dsNamespacedName *types.NamespacedName) *robotv1alpha1.DiscoveryServer { +func GetROS2WorkloadDiscoveryServer(ros2Workload *robotv1alpha2.ROS2Workload, dsNamespacedName *types.NamespacedName) *robotv1alpha1.DiscoveryServer { discoveryServer := robotv1alpha1.DiscoveryServer{ ObjectMeta: metav1.ObjectMeta{ @@ -27,7 +27,7 @@ func GetDiscoveryServer(ros2Workload *robotv1alpha2.ROS2Workload, dsNamespacedNa } -func GetROS2Bridge(ros2Workload *robotv1alpha2.ROS2Workload, r2bNamespacedName *types.NamespacedName) *robotv1alpha2.ROS2Bridge { +func GetROS2WorkloadROS2Bridge(ros2Workload *robotv1alpha2.ROS2Workload, r2bNamespacedName *types.NamespacedName) *robotv1alpha2.ROS2Bridge { ros2Bridge := robotv1alpha2.ROS2Bridge{ ObjectMeta: metav1.ObjectMeta{ @@ -42,7 +42,7 @@ func GetROS2Bridge(ros2Workload *robotv1alpha2.ROS2Workload, r2bNamespacedName * } -func GetPersistentVolumeClaim(ros2Workload *robotv1alpha2.ROS2Workload, pvcNamespacedName *types.NamespacedName, key int) *corev1.PersistentVolumeClaim { +func GetROS2WorkloadPersistentVolumeClaim(ros2Workload *robotv1alpha2.ROS2Workload, pvcNamespacedName *types.NamespacedName, key int) *corev1.PersistentVolumeClaim { pvc := corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ @@ -57,7 +57,7 @@ func GetPersistentVolumeClaim(ros2Workload *robotv1alpha2.ROS2Workload, pvcNames } -func GetStatefulSet(ros2Workload *robotv1alpha2.ROS2Workload, ssNamespacedName *types.NamespacedName, key int, node corev1.Node) *appsv1.StatefulSet { +func GetROS2WorkloadStatefulSet(ros2Workload *robotv1alpha2.ROS2Workload, ssNamespacedName *types.NamespacedName, key int, node corev1.Node) *appsv1.StatefulSet { cfg := configure.PodSpecConfigInjector{} @@ -77,8 +77,8 @@ func GetStatefulSet(ros2Workload *robotv1alpha2.ROS2Workload, ssNamespacedName * cfg.InjectImagePullPolicy(&podSpec) cfg.SchedulePod(&podSpec, ros2Workload) cfg.InjectTimezone(&podSpec, node) - cfg.InjectRuntimeClass(&podSpec, *ros2Workload, node) - cfg.InjectVolumeConfiguration(&podSpec, *ros2Workload) + cfg.InjectRuntimeClass(&podSpec, ros2Workload, node) + cfg.InjectVolumeConfiguration(&podSpec, ros2Workload.Status.PVCStatuses) statefulSet := appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ diff --git a/internal/shared.go b/internal/shared.go index 43a7664c..a12dcd7c 100644 --- a/internal/shared.go +++ b/internal/shared.go @@ -21,6 +21,7 @@ const ( // Selector labels const ( ROS2_WORKLOAD_CONTAINER_SELECTOR_LABEL_KEY = "robolaunch.io/ros2-workload-container" + CODE_EDITOR_CONTAINER_SELECTOR_LABEL_KEY = "robolaunch.io/code-editor-container" ) // Timezone labels @@ -73,6 +74,7 @@ const ( ROS_2_BRIDGE_POSTFIX = "-bridge" PVC_POSTFIX = "-pvc" STATEFULSET_POSTFIX = "" + DEPLOYMENT_POSTFIX = "" ) // WorkspaceManager owned resources' postfixes diff --git a/main.go b/main.go index 13826ab4..66217c3f 100644 --- a/main.go +++ b/main.go @@ -51,6 +51,7 @@ import ( robotVDI "github.com/robolaunch/robot-operator/pkg/controllers/v1alpha1/robot_dev_suite/robot_vdi" workspaceManager "github.com/robolaunch/robot-operator/pkg/controllers/v1alpha1/workspace_manager" ros2Workload "github.com/robolaunch/robot-operator/pkg/controllers/v1alpha2/production/ros2_workload" + codeEditor "github.com/robolaunch/robot-operator/pkg/controllers/v1alpha2/toolkit/code_editor" ros2Bridge "github.com/robolaunch/robot-operator/pkg/controllers/v1alpha2/toolkit/ros2_bridge" //+kubebuilder:scaffold:imports ) @@ -128,8 +129,8 @@ func main() { startDevCRDsAndWebhooks(mgr, dynamicClient) startObserverCRDsAndWebhooks(mgr, dynamicClient) // v1alpha2 - startProductionCRDsAndWebhooks(mgr, dynamicClient) - startToolkitCRDsAndWebhooks(mgr, dynamicClient) + startProductionCRDsAndWebhooks(mgr) + startToolkitCRDsAndWebhooks(mgr) //+kubebuilder:scaffold:builder @@ -331,7 +332,8 @@ func startObserverCRDsAndWebhooks(mgr manager.Manager, dynamicClient dynamic.Int // This function starts Production CRDs' controllers and webhooks. Here are the CRDs: // - ROS2Workload (ros2workloads.robot.roboscale.io/v1alpha2) -func startProductionCRDsAndWebhooks(mgr manager.Manager, dynamicClient dynamic.Interface) { +func startProductionCRDsAndWebhooks(mgr manager.Manager) { + // ROS2Workload controller & webhook if err := (&ros2Workload.ROS2WorkloadReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), @@ -347,7 +349,8 @@ func startProductionCRDsAndWebhooks(mgr manager.Manager, dynamicClient dynamic.I // This function starts Toolkit CRDs' controllers and webhooks. Here are the CRDs: // - ROS2Bridge (ros2bridges.robot.roboscale.io/v1alpha2) -func startToolkitCRDsAndWebhooks(mgr manager.Manager, dynamicClient dynamic.Interface) { +func startToolkitCRDsAndWebhooks(mgr manager.Manager) { + // ROS2Bridge controller & webhook if err := (&ros2Bridge.ROS2BridgeReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), @@ -359,4 +362,17 @@ func startToolkitCRDsAndWebhooks(mgr manager.Manager, dynamicClient dynamic.Inte setupLog.Error(err, "unable to create webhook", "webhook", "ROS2Bridge") os.Exit(1) } + + // ROS2Bridge controller & webhook + if err := (&codeEditor.CodeEditorReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "CodeEditor") + os.Exit(1) + } + if err := (&robotv1alpha2.CodeEditor{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "CodeEditor") + os.Exit(1) + } } diff --git a/pkg/api/roboscale.io/v1alpha2/phases.go b/pkg/api/roboscale.io/v1alpha2/phases.go index 0be28793..b23557a7 100644 --- a/pkg/api/roboscale.io/v1alpha2/phases.go +++ b/pkg/api/roboscale.io/v1alpha2/phases.go @@ -20,3 +20,11 @@ const ( ROS2BridgePhaseDeletingPod ROS2BridgePhase = "DeletingPod" ROS2BridgePhaseDeletingService ROS2BridgePhase = "DeletingService" ) + +type CodeEditorPhase string + +const ( + CodeEditorPhaseCreatingPVCs CodeEditorPhase = "CreatingPVCs" + CodeEditorPhaseCreatingDeployment CodeEditorPhase = "CreatingDeployment" + CodeEditorPhaseReady CodeEditorPhase = "Ready" +) diff --git a/pkg/api/roboscale.io/v1alpha2/shared_types.go b/pkg/api/roboscale.io/v1alpha2/shared_types.go index 529c13ea..f8984dae 100644 --- a/pkg/api/roboscale.io/v1alpha2/shared_types.go +++ b/pkg/api/roboscale.io/v1alpha2/shared_types.go @@ -34,8 +34,17 @@ type OwnedPVCStatus struct { type OwnedStatefulSetStatus struct { // Generic status for any owned resource. Resource OwnedResourceStatus `json:"resource,omitempty"` - // Status of the ROS2Bridge instance. + // Status of the StatefulSet. Status appsv1.StatefulSetStatus `json:"status,omitempty"` // Container statuses. ContainerStatuses []corev1.ContainerStatus `json:"containerStatuses,omitempty"` } + +type OwnedDeploymentStatus struct { + // Generic status for any owned resource. + Resource OwnedResourceStatus `json:"resource,omitempty"` + // Status of the Deployment. + Status appsv1.DeploymentStatus `json:"status,omitempty"` + // Container statuses. + ContainerStatuses []corev1.ContainerStatus `json:"containerStatuses,omitempty"` +} diff --git a/pkg/api/roboscale.io/v1alpha2/toolkit_helpers.go b/pkg/api/roboscale.io/v1alpha2/toolkit_helpers.go index 6c215687..242f91e8 100644 --- a/pkg/api/roboscale.io/v1alpha2/toolkit_helpers.go +++ b/pkg/api/roboscale.io/v1alpha2/toolkit_helpers.go @@ -1,6 +1,11 @@ package v1alpha2 -import "k8s.io/apimachinery/pkg/types" +import ( + "strconv" + + "github.com/robolaunch/robot-operator/internal" + "k8s.io/apimachinery/pkg/types" +) // ******************************** // ROS2Bridge helpers @@ -26,3 +31,21 @@ func (ros2bridge *ROS2Bridge) GetROS2BridgeIngressMetadata() *types.NamespacedNa Namespace: ros2bridge.Namespace, } } + +// ******************************** +// CodeEditor helpers +// ******************************* + +func (codeEditor *CodeEditor) GetPersistentVolumeClaimMetadata(key int) *types.NamespacedName { + return &types.NamespacedName{ + Name: codeEditor.Name + internal.PVC_POSTFIX + "-" + strconv.Itoa(key), + Namespace: codeEditor.Namespace, + } +} + +func (codeEditor *CodeEditor) GetDeploymentMetadata() *types.NamespacedName { + return &types.NamespacedName{ + Name: codeEditor.Name + internal.DEPLOYMENT_POSTFIX, + Namespace: codeEditor.Namespace, + } +} diff --git a/pkg/api/roboscale.io/v1alpha2/toolkit_types.go b/pkg/api/roboscale.io/v1alpha2/toolkit_types.go index 8b3df4e2..aa5e5421 100644 --- a/pkg/api/roboscale.io/v1alpha2/toolkit_types.go +++ b/pkg/api/roboscale.io/v1alpha2/toolkit_types.go @@ -24,6 +24,7 @@ import ( func init() { SchemeBuilder.Register(&ROS2Bridge{}, &ROS2BridgeList{}) + SchemeBuilder.Register(&CodeEditor{}, &CodeEditorList{}) } //+genclient @@ -49,6 +50,29 @@ type ROS2BridgeList struct { Items []ROS2Bridge `json:"items"` } +//+genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// CodeEditor is the Schema for the codeeditors API +type CodeEditor struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + // Specification of the desired behavior of the CodeEditor. + Spec CodeEditorSpec `json:"spec,omitempty"` + // Most recently observed status of the CodeEditor. + Status CodeEditorStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// CodeEditorList contains a list of CodeEditor +type CodeEditorList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CodeEditor `json:"items"` +} + // ******************************** // ROS2Bridge types // ******************************** @@ -83,3 +107,52 @@ type ROS2BridgeStatus struct { // Status of ROS2Bridge Ingress. IngressStatus robotv1alpha1.OwnedResourceStatus `json:"ingressStatus,omitempty"` } + +// ******************************** +// CodeEditor types +// ******************************** + +type ExternalVolumeStatus struct { + // Name of the external volume. + Name string `json:"name,omitempty"` + // Indicates if the volume exists. + Exists bool `json:"exists,omitempty"` +} + +type CodeEditorContainer struct { + // Security context of the code editor container. + SecurityContext corev1.SecurityContext `json:"securityContext,omitempty"` + // Mounted volumes of the code editor container. + VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` +} + +// CodeEditorSpec defines the desired state of CodeEditor. +type CodeEditorSpec struct { + // If `true`, code editor will be consumed as `root` user. + Root bool `json:"root,omitempty"` + // If `true`, code editor will be consumed remotely. + Remote bool `json:"remote,omitempty"` + // Configurational parameters for code editor container. + Container CodeEditorContainer `json:"container,omitempty"` + // Port that code editor will use inside the container. + // +kubebuilder:default=9000 + Port int32 `json:"port"` + // Volume templates for ROS 2 workload. + // For each volume template, operator will create a PersistentVolumeClaim + // that can be mounted to the ROS 2 workload. + VolumeClaimTemplates []corev1.PersistentVolumeClaimTemplate `json:"volumeClaimTemplates,omitempty"` + // External volumes. + ExternalVolumes []corev1.Volume `json:"externalVolumes,omitempty"` +} + +// CodeEditorStatus defines the observed state of CodeEditor. +type CodeEditorStatus struct { + // Phase of CodeEditor. It sums the general status of code editor. + Phase CodeEditorPhase `json:"phase,omitempty"` + // Statuses of owned PersistentVolumeClaims. + PVCStatuses []OwnedPVCStatus `json:"pvcStatuses,omitempty"` + // Statuses of external volumes. + ExternalVolumeStatuses []ExternalVolumeStatus `json:"externalVolumeStatuses,omitempty"` + // Status of code editor deployment. + DeploymentStatus OwnedDeploymentStatus `json:"deploymentStatus,omitempty"` +} diff --git a/pkg/api/roboscale.io/v1alpha2/toolkit_webhook.go b/pkg/api/roboscale.io/v1alpha2/toolkit_webhook.go index 980e947b..196f745e 100644 --- a/pkg/api/roboscale.io/v1alpha2/toolkit_webhook.go +++ b/pkg/api/roboscale.io/v1alpha2/toolkit_webhook.go @@ -75,3 +75,47 @@ func (r *ROS2Bridge) ValidateDelete() error { ros2bridgelog.Info("validate delete", "name", r.Name) return nil } + +// ******************************** +// CodeEditor webhooks +// ******************************** + +// log is for logging in this package. +var codeeditorlog = logf.Log.WithName("codeeditor-resource") + +func (r *CodeEditor) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//+kubebuilder:webhook:path=/mutate-robot-roboscale-io-v1alpha2-codeeditor,mutating=true,failurePolicy=fail,sideEffects=None,groups=robot.roboscale.io,resources=codeeditors,verbs=create;update,versions=v1alpha2,name=mcodeeditor.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &CodeEditor{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (r *CodeEditor) Default() { + codeeditorlog.Info("default", "name", r.Name) +} + +//+kubebuilder:webhook:path=/validate-robot-roboscale-io-v1alpha2-codeeditor,mutating=false,failurePolicy=fail,sideEffects=None,groups=robot.roboscale.io,resources=codeeditors,verbs=create;update,versions=v1alpha2,name=vcodeeditor.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &CodeEditor{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *CodeEditor) ValidateCreate() error { + codeeditorlog.Info("validate create", "name", r.Name) + return nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *CodeEditor) ValidateUpdate(old runtime.Object) error { + codeeditorlog.Info("validate update", "name", r.Name) + return nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *CodeEditor) ValidateDelete() error { + codeeditorlog.Info("validate delete", "name", r.Name) + return nil +} diff --git a/pkg/api/roboscale.io/v1alpha2/zz_generated.deepcopy.go b/pkg/api/roboscale.io/v1alpha2/zz_generated.deepcopy.go index a8c95d29..e663b3cc 100644 --- a/pkg/api/roboscale.io/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/api/roboscale.io/v1alpha2/zz_generated.deepcopy.go @@ -26,6 +26,161 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CodeEditor) DeepCopyInto(out *CodeEditor) { + *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 CodeEditor. +func (in *CodeEditor) DeepCopy() *CodeEditor { + if in == nil { + return nil + } + out := new(CodeEditor) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CodeEditor) 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 *CodeEditorContainer) DeepCopyInto(out *CodeEditorContainer) { + *out = *in + in.SecurityContext.DeepCopyInto(&out.SecurityContext) + if in.VolumeMounts != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]v1.VolumeMount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CodeEditorContainer. +func (in *CodeEditorContainer) DeepCopy() *CodeEditorContainer { + if in == nil { + return nil + } + out := new(CodeEditorContainer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CodeEditorList) DeepCopyInto(out *CodeEditorList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CodeEditor, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CodeEditorList. +func (in *CodeEditorList) DeepCopy() *CodeEditorList { + if in == nil { + return nil + } + out := new(CodeEditorList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CodeEditorList) 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 *CodeEditorSpec) DeepCopyInto(out *CodeEditorSpec) { + *out = *in + in.Container.DeepCopyInto(&out.Container) + if in.VolumeClaimTemplates != nil { + in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates + *out = make([]v1.PersistentVolumeClaimTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ExternalVolumes != nil { + in, out := &in.ExternalVolumes, &out.ExternalVolumes + *out = make([]v1.Volume, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CodeEditorSpec. +func (in *CodeEditorSpec) DeepCopy() *CodeEditorSpec { + if in == nil { + return nil + } + out := new(CodeEditorSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CodeEditorStatus) DeepCopyInto(out *CodeEditorStatus) { + *out = *in + if in.PVCStatuses != nil { + in, out := &in.PVCStatuses, &out.PVCStatuses + *out = make([]OwnedPVCStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ExternalVolumeStatuses != nil { + in, out := &in.ExternalVolumeStatuses, &out.ExternalVolumeStatuses + *out = make([]ExternalVolumeStatus, len(*in)) + copy(*out, *in) + } + in.DeploymentStatus.DeepCopyInto(&out.DeploymentStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CodeEditorStatus. +func (in *CodeEditorStatus) DeepCopy() *CodeEditorStatus { + if in == nil { + return nil + } + out := new(CodeEditorStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalVolumeStatus) DeepCopyInto(out *ExternalVolumeStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalVolumeStatus. +func (in *ExternalVolumeStatus) DeepCopy() *ExternalVolumeStatus { + if in == nil { + return nil + } + out := new(ExternalVolumeStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LaunchContainer) DeepCopyInto(out *LaunchContainer) { *out = *in @@ -47,6 +202,30 @@ func (in *LaunchContainer) DeepCopy() *LaunchContainer { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OwnedDeploymentStatus) DeepCopyInto(out *OwnedDeploymentStatus) { + *out = *in + out.Resource = in.Resource + in.Status.DeepCopyInto(&out.Status) + if in.ContainerStatuses != nil { + in, out := &in.ContainerStatuses, &out.ContainerStatuses + *out = make([]v1.ContainerStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnedDeploymentStatus. +func (in *OwnedDeploymentStatus) DeepCopy() *OwnedDeploymentStatus { + if in == nil { + return nil + } + out := new(OwnedDeploymentStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OwnedPVCStatus) DeepCopyInto(out *OwnedPVCStatus) { *out = *in diff --git a/pkg/controllers/v1alpha2/production/ros2_workload/check.go b/pkg/controllers/v1alpha2/production/ros2_workload/check.go index 13230be5..cd6d2fbe 100644 --- a/pkg/controllers/v1alpha2/production/ros2_workload/check.go +++ b/pkg/controllers/v1alpha2/production/ros2_workload/check.go @@ -96,7 +96,7 @@ func (r *ROS2WorkloadReconciler) reconcileCheckStatefulSets(ctx context.Context, volumesReady = volumesReady && pvcStatus.Resource.Created } - if instance.Status.DiscoveryServerStatus.Status.ConfigMapStatus.Created && volumesReady { + if instance.Status.DiscoveryServerStatus.Status.ConfigMapStatus.Created && volumesReady && len(instance.Spec.LaunchContainers) == len(instance.Status.StatefulSetStatuses) { for key, ssStatus := range instance.Status.StatefulSetStatuses { ssQuery := &appsv1.StatefulSet{} @@ -109,6 +109,10 @@ func (r *ROS2WorkloadReconciler) reconcileCheckStatefulSets(ctx context.Context, // check if there are any inconsistencies launchContainer := instance.Spec.LaunchContainers[key] actualContainer := ssQuery.Spec.Template.Spec.Containers[0] + actualContainerStatus := corev1.ContainerStatus{} + if len(ssStatus.ContainerStatuses) > 0 { + actualContainerStatus = ssStatus.ContainerStatuses[0] + } volumeMountsSynced := true for _, vmDesired := range launchContainer.Container.VolumeMounts { @@ -121,21 +125,41 @@ func (r *ROS2WorkloadReconciler) reconcileCheckStatefulSets(ctx context.Context, volumeMountsSynced = volumeMountsSynced && volumePresent } + envSynced := true + for _, envDesired := range launchContainer.Container.Env { + envPresent := false + for _, envActual := range actualContainer.Env { + if reflect.DeepEqual(envDesired, envActual) { + envPresent = true + } + } + envSynced = envSynced && envPresent + } + // changes that needs resource updates if !reflect.DeepEqual(launchContainer.Replicas, ssQuery.Spec.Replicas) || !reflect.DeepEqual(launchContainer.Container.Image, actualContainer.Image) || !reflect.DeepEqual(launchContainer.Container.Command, actualContainer.Command) || !reflect.DeepEqual(launchContainer.Container.Resources, actualContainer.Resources) || - !reflect.DeepEqual(launchContainer.Container.SecurityContext, actualContainer.SecurityContext) { + !reflect.DeepEqual(launchContainer.Container.SecurityContext, actualContainer.SecurityContext) || + !envSynced { - err = r.updateStatefulSet(ctx, instance, key) - if err != nil { - return err + if !actualContainerStatus.Ready { + err = r.Delete(ctx, ssQuery) + if err != nil { + return err + } + + ssStatus.Resource.Created = false + ssStatus.Status = appsv1.StatefulSetStatus{} + ssStatus.ContainerStatuses = []corev1.ContainerStatus{} + } else { + err = r.updateStatefulSet(ctx, instance, key) + if err != nil { + return err + } } - ssStatus.Resource.Created = false - ssStatus.Status = appsv1.StatefulSetStatus{} - ssStatus.ContainerStatuses = []corev1.ContainerStatus{} continue } diff --git a/pkg/controllers/v1alpha2/production/ros2_workload/create.go b/pkg/controllers/v1alpha2/production/ros2_workload/create.go index af52042f..fe969ca4 100644 --- a/pkg/controllers/v1alpha2/production/ros2_workload/create.go +++ b/pkg/controllers/v1alpha2/production/ros2_workload/create.go @@ -12,7 +12,7 @@ import ( func (r *ROS2WorkloadReconciler) createDiscoveryServer(ctx context.Context, instance *robotv1alpha2.ROS2Workload) error { - discoveryServer := v1alpha2_resources.GetDiscoveryServer(instance, instance.GetDiscoveryServerMetadata()) + discoveryServer := v1alpha2_resources.GetROS2WorkloadDiscoveryServer(instance, instance.GetDiscoveryServerMetadata()) err := ctrl.SetControllerReference(instance, discoveryServer, r.Scheme) if err != nil { @@ -32,7 +32,7 @@ func (r *ROS2WorkloadReconciler) createDiscoveryServer(ctx context.Context, inst func (r *ROS2WorkloadReconciler) createROS2Bridge(ctx context.Context, instance *robotv1alpha2.ROS2Workload) error { - ros2Bridge := v1alpha2_resources.GetROS2Bridge(instance, instance.GetROS2BridgeMetadata()) + ros2Bridge := v1alpha2_resources.GetROS2WorkloadROS2Bridge(instance, instance.GetROS2BridgeMetadata()) err := ctrl.SetControllerReference(instance, ros2Bridge, r.Scheme) if err != nil { @@ -52,7 +52,7 @@ func (r *ROS2WorkloadReconciler) createROS2Bridge(ctx context.Context, instance func (r *ROS2WorkloadReconciler) createPersistentVolumeClaim(ctx context.Context, instance *robotv1alpha2.ROS2Workload, key int) error { - pvc := v1alpha2_resources.GetPersistentVolumeClaim(instance, instance.GetPersistentVolumeClaimMetadata(key), key) + pvc := v1alpha2_resources.GetROS2WorkloadPersistentVolumeClaim(instance, instance.GetPersistentVolumeClaimMetadata(key), key) err := ctrl.SetControllerReference(instance, pvc, r.Scheme) if err != nil { @@ -77,7 +77,7 @@ func (r *ROS2WorkloadReconciler) createStatefulSet(ctx context.Context, instance return err } - statefulSet := v1alpha2_resources.GetStatefulSet(instance, instance.GetStatefulSetMetadata(key), key, *node) + statefulSet := v1alpha2_resources.GetROS2WorkloadStatefulSet(instance, instance.GetStatefulSetMetadata(key), key, *node) err = ctrl.SetControllerReference(instance, statefulSet, r.Scheme) if err != nil { diff --git a/pkg/controllers/v1alpha2/production/ros2_workload/register.go b/pkg/controllers/v1alpha2/production/ros2_workload/register.go index 219025ed..70a75fc6 100644 --- a/pkg/controllers/v1alpha2/production/ros2_workload/register.go +++ b/pkg/controllers/v1alpha2/production/ros2_workload/register.go @@ -1,11 +1,14 @@ package ros2_workload import ( + "context" + robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" ) -func (r *ROS2WorkloadReconciler) registerPVCs(instance *robotv1alpha2.ROS2Workload) { +func (r *ROS2WorkloadReconciler) registerPVCs(ctx context.Context, instance *robotv1alpha2.ROS2Workload) error { pvcStatuses := []robotv1alpha2.OwnedPVCStatus{} @@ -23,13 +26,31 @@ func (r *ROS2WorkloadReconciler) registerPVCs(instance *robotv1alpha2.ROS2Worklo } instance.Status.PVCStatuses = pvcStatuses } + + return nil } -func (r *ROS2WorkloadReconciler) registerStatefulSets(instance *robotv1alpha2.ROS2Workload) { +func (r *ROS2WorkloadReconciler) registerStatefulSets(ctx context.Context, instance *robotv1alpha2.ROS2Workload) error { ssStatuses := []robotv1alpha2.OwnedStatefulSetStatus{} if len(instance.Spec.LaunchContainers) != len(instance.Status.StatefulSetStatuses) { + + if len(instance.Status.StatefulSetStatuses) > len(instance.Spec.LaunchContainers) { + for key := len(instance.Spec.LaunchContainers); key < len(instance.Status.StatefulSetStatuses); key++ { + statefulSet := appsv1.StatefulSet{} + err := r.Get(ctx, *instance.GetStatefulSetMetadata(key), &statefulSet) + if err != nil { + return err + } + + err = r.Delete(ctx, &statefulSet) + if err != nil { + return err + } + } + } + for key := range instance.Spec.LaunchContainers { ssStatus := robotv1alpha2.OwnedStatefulSetStatus{ Resource: robotv1alpha2.OwnedResourceStatus{ @@ -43,4 +64,6 @@ func (r *ROS2WorkloadReconciler) registerStatefulSets(instance *robotv1alpha2.RO } instance.Status.StatefulSetStatuses = ssStatuses } + + return nil } diff --git a/pkg/controllers/v1alpha2/production/ros2_workload/ros2workload_controller.go b/pkg/controllers/v1alpha2/production/ros2_workload/ros2workload_controller.go index a6e9dbbb..603970a4 100644 --- a/pkg/controllers/v1alpha2/production/ros2_workload/ros2workload_controller.go +++ b/pkg/controllers/v1alpha2/production/ros2_workload/ros2workload_controller.go @@ -63,7 +63,10 @@ func (r *ROS2WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request return ctrl.Result{}, err } - r.reconcileRegisterResources(instance) + err = r.reconcileRegisterResources(ctx, instance) + if err != nil { + return ctrl.Result{}, err + } err = r.reconcileUpdateInstanceStatus(ctx, instance) if err != nil { @@ -93,10 +96,17 @@ func (r *ROS2WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request return result, nil } -func (r *ROS2WorkloadReconciler) reconcileRegisterResources(instance *robotv1alpha2.ROS2Workload) error { +func (r *ROS2WorkloadReconciler) reconcileRegisterResources(ctx context.Context, instance *robotv1alpha2.ROS2Workload) error { + + err := r.registerPVCs(ctx, instance) + if err != nil { + return err + } - r.registerPVCs(instance) - r.registerStatefulSets(instance) + err = r.registerStatefulSets(ctx, instance) + if err != nil { + return err + } return nil } diff --git a/pkg/controllers/v1alpha2/production/ros2_workload/update.go b/pkg/controllers/v1alpha2/production/ros2_workload/update.go index 935bf06d..d866e8ed 100644 --- a/pkg/controllers/v1alpha2/production/ros2_workload/update.go +++ b/pkg/controllers/v1alpha2/production/ros2_workload/update.go @@ -12,7 +12,7 @@ import ( func (r *ROS2WorkloadReconciler) updateDiscoveryServer(ctx context.Context, instance *robotv1alpha2.ROS2Workload) error { - discoveryServer := v1alpha2_resources.GetDiscoveryServer(instance, instance.GetDiscoveryServerMetadata()) + discoveryServer := v1alpha2_resources.GetROS2WorkloadDiscoveryServer(instance, instance.GetDiscoveryServerMetadata()) err := ctrl.SetControllerReference(instance, discoveryServer, r.Scheme) if err != nil { @@ -32,7 +32,7 @@ func (r *ROS2WorkloadReconciler) updateDiscoveryServer(ctx context.Context, inst func (r *ROS2WorkloadReconciler) updateROS2Bridge(ctx context.Context, instance *robotv1alpha2.ROS2Workload) error { - ros2Bridge := v1alpha2_resources.GetROS2Bridge(instance, instance.GetROS2BridgeMetadata()) + ros2Bridge := v1alpha2_resources.GetROS2WorkloadROS2Bridge(instance, instance.GetROS2BridgeMetadata()) err := ctrl.SetControllerReference(instance, ros2Bridge, r.Scheme) if err != nil { @@ -57,7 +57,7 @@ func (r *ROS2WorkloadReconciler) updateStatefulSet(ctx context.Context, instance return err } - statefulSet := v1alpha2_resources.GetStatefulSet(instance, instance.GetStatefulSetMetadata(key), key, *node) + statefulSet := v1alpha2_resources.GetROS2WorkloadStatefulSet(instance, instance.GetStatefulSetMetadata(key), key, *node) err = ctrl.SetControllerReference(instance, statefulSet, r.Scheme) if err != nil { diff --git a/pkg/controllers/v1alpha2/toolkit/code_editor/check.go b/pkg/controllers/v1alpha2/toolkit/code_editor/check.go new file mode 100644 index 00000000..61c7049c --- /dev/null +++ b/pkg/controllers/v1alpha2/toolkit/code_editor/check.go @@ -0,0 +1,78 @@ +package code_editor + +import ( + "context" + + "github.com/robolaunch/robot-operator/internal/reference" + robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" +) + +func (r *CodeEditorReconciler) reconcileCheckPVCs(ctx context.Context, instance *robotv1alpha2.CodeEditor) error { + + for key, pvcStatus := range instance.Status.PVCStatuses { + + pvcQuery := &corev1.PersistentVolumeClaim{} + err := r.Get(ctx, *instance.GetPersistentVolumeClaimMetadata(key), pvcQuery) + if err != nil && errors.IsNotFound(err) { + pvcStatus.Resource.Created = false + } else if err != nil { + return err + } else { + pvcStatus.Resource.Created = true + reference.SetReference(&pvcStatus.Resource.Reference, pvcQuery.TypeMeta, pvcQuery.ObjectMeta) + pvcStatus.Status = pvcQuery.Status + } + + instance.Status.PVCStatuses[key] = pvcStatus + + } + + return nil +} + +func (r *CodeEditorReconciler) reconcileCheckExternalVolumes(ctx context.Context, instance *robotv1alpha2.CodeEditor) error { + + for key, evStatus := range instance.Status.ExternalVolumeStatuses { + + pvcQuery := &corev1.PersistentVolumeClaim{} + err := r.Get(ctx, types.NamespacedName{ + Namespace: instance.Namespace, + Name: evStatus.Name, + }, pvcQuery) + if err != nil && errors.IsNotFound(err) { + evStatus.Exists = false + } else if err != nil { + return err + } else { + evStatus.Exists = true + } + + instance.Status.ExternalVolumeStatuses[key] = evStatus + + } + + return nil +} + +func (r *CodeEditorReconciler) reconcileCheckDeployment(ctx context.Context, instance *robotv1alpha2.CodeEditor) error { + + deploymentQuery := &appsv1.Deployment{} + err := r.Get(ctx, *instance.GetDeploymentMetadata(), deploymentQuery) + if err != nil && errors.IsNotFound(err) { + instance.Status.DeploymentStatus = robotv1alpha2.OwnedDeploymentStatus{} + } else if err != nil { + return err + } else { + + // make deployment dynamic + + instance.Status.DeploymentStatus.Resource.Created = true + reference.SetReference(&instance.Status.DeploymentStatus.Resource.Reference, deploymentQuery.TypeMeta, deploymentQuery.ObjectMeta) + } + + return nil +} diff --git a/pkg/controllers/v1alpha2/toolkit/code_editor/codeeditor_controller.go b/pkg/controllers/v1alpha2/toolkit/code_editor/codeeditor_controller.go new file mode 100644 index 00000000..3eb22eb6 --- /dev/null +++ b/pkg/controllers/v1alpha2/toolkit/code_editor/codeeditor_controller.go @@ -0,0 +1,143 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package code_editor + +import ( + "context" + + robotErr "github.com/robolaunch/robot-operator/internal/error" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/go-logr/logr" + robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2" +) + +// CodeEditorReconciler reconciles a CodeEditor object +type CodeEditorReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=robot.roboscale.io,resources=codeeditors,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=robot.roboscale.io,resources=codeeditors/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=robot.roboscale.io,resources=codeeditors/finalizers,verbs=update + +//+kubebuilder:rbac:groups=core,resources=persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete + +var logger logr.Logger + +func (r *CodeEditorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger = log.FromContext(ctx) + + var result ctrl.Result = ctrl.Result{} + + instance, err := r.reconcileGetInstance(ctx, req.NamespacedName) + if err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + r.reconcileRegisterResources(instance) + + err = r.reconcileUpdateInstanceStatus(ctx, instance) + if err != nil { + return ctrl.Result{}, err + } + + err = r.reconcileCheckStatus(ctx, instance, &result) + if err != nil { + return result, err + } + + err = r.reconcileUpdateInstanceStatus(ctx, instance) + if err != nil { + return ctrl.Result{}, err + } + + err = r.reconcileCheckResources(ctx, instance) + if err != nil { + return ctrl.Result{}, err + } + + err = r.reconcileUpdateInstanceStatus(ctx, instance) + if err != nil { + return ctrl.Result{}, err + } + + return result, nil +} + +func (r *CodeEditorReconciler) reconcileRegisterResources(instance *robotv1alpha2.CodeEditor) error { + + r.registerPVCs(instance) + r.registerExternalVolumes(instance) + + return nil +} + +func (r *CodeEditorReconciler) reconcileCheckStatus(ctx context.Context, instance *robotv1alpha2.CodeEditor, result *ctrl.Result) error { + + err := r.reconcileHandlePVCs(ctx, instance) + if err != nil { + return robotErr.CheckCreatingOrWaitingError(result, err) + } + + err = r.reconcileHandleDeployment(ctx, instance) + if err != nil { + return robotErr.CheckCreatingOrWaitingError(result, err) + } + + return nil +} + +func (r *CodeEditorReconciler) reconcileCheckResources(ctx context.Context, instance *robotv1alpha2.CodeEditor) error { + + err := r.reconcileCheckPVCs(ctx, instance) + if err != nil { + return err + } + + err = r.reconcileCheckExternalVolumes(ctx, instance) + if err != nil { + return err + } + + err = r.reconcileCheckDeployment(ctx, instance) + if err != nil { + return err + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *CodeEditorReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&robotv1alpha2.CodeEditor{}). + Owns(&corev1.PersistentVolumeClaim{}). + Owns(&appsv1.Deployment{}). + Complete(r) +} diff --git a/pkg/controllers/v1alpha2/toolkit/code_editor/create.go b/pkg/controllers/v1alpha2/toolkit/code_editor/create.go new file mode 100644 index 00000000..2992302e --- /dev/null +++ b/pkg/controllers/v1alpha2/toolkit/code_editor/create.go @@ -0,0 +1,55 @@ +package code_editor + +import ( + "context" + + v1alpha2_resources "github.com/robolaunch/robot-operator/internal/resources/v1alpha2" + robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2" + "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" +) + +func (r *CodeEditorReconciler) createPersistentVolumeClaim(ctx context.Context, instance *robotv1alpha2.CodeEditor, key int) error { + + pvc := v1alpha2_resources.GetCodeEditorPersistentVolumeClaim(instance, instance.GetPersistentVolumeClaimMetadata(key), key) + + err := ctrl.SetControllerReference(instance, pvc, r.Scheme) + if err != nil { + return err + } + + err = r.Create(ctx, pvc) + if err != nil && errors.IsAlreadyExists(err) { + return nil + } else if err != nil { + return err + } + + logger.Info("STATUS: PVC " + instance.GetPersistentVolumeClaimMetadata(key).Name + " is created.") + return nil +} + +func (r *CodeEditorReconciler) createDeployment(ctx context.Context, instance *robotv1alpha2.CodeEditor) error { + + node, err := r.reconcileGetNode(ctx, instance) + if err != nil { + return err + } + + deployment := v1alpha2_resources.GetCodeEditorDeployment(instance, instance.GetDeploymentMetadata(), *node) + + err = ctrl.SetControllerReference(instance, deployment, r.Scheme) + if err != nil { + return err + } + + err = r.Create(ctx, deployment) + if err != nil && errors.IsAlreadyExists(err) { + return nil + } else if err != nil { + return err + } + + logger.Info("STATUS: Deployment " + instance.GetDeploymentMetadata().Name + " is created.") + return nil +} diff --git a/pkg/controllers/v1alpha2/toolkit/code_editor/handle.go b/pkg/controllers/v1alpha2/toolkit/code_editor/handle.go new file mode 100644 index 00000000..78cc7a98 --- /dev/null +++ b/pkg/controllers/v1alpha2/toolkit/code_editor/handle.go @@ -0,0 +1,65 @@ +package code_editor + +import ( + "context" + + robotErr "github.com/robolaunch/robot-operator/internal/error" + + robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2" +) + +func (r *CodeEditorReconciler) reconcileHandlePVCs(ctx context.Context, instance *robotv1alpha2.CodeEditor) error { + + for key, pvcStatus := range instance.Status.PVCStatuses { + if !pvcStatus.Resource.Created { + + instance.Status.Phase = robotv1alpha2.CodeEditorPhaseCreatingPVCs + err := r.createPersistentVolumeClaim(ctx, instance, key) + if err != nil { + return err + } + + pvcStatus.Resource.Created = true + instance.Status.PVCStatuses[key] = pvcStatus + + return &robotErr.CreatingResourceError{ + ResourceKind: "PersistentVolumeClaim", + ResourceName: instance.GetPersistentVolumeClaimMetadata(key).Name, + ResourceNamespace: instance.GetPersistentVolumeClaimMetadata(key).Namespace, + } + } + } + + return nil +} + +func (r *CodeEditorReconciler) reconcileHandleDeployment(ctx context.Context, instance *robotv1alpha2.CodeEditor) error { + + volumesReady := true + for _, pvcStatus := range instance.Status.PVCStatuses { + volumesReady = volumesReady && pvcStatus.Resource.Created + } + + for _, evStatus := range instance.Status.ExternalVolumeStatuses { + volumesReady = volumesReady && evStatus.Exists + } + + if volumesReady && !instance.Status.DeploymentStatus.Resource.Created { + + instance.Status.Phase = robotv1alpha2.CodeEditorPhaseCreatingDeployment + err := r.createDeployment(ctx, instance) + if err != nil { + return err + } + + instance.Status.DeploymentStatus.Resource.Created = true + + return &robotErr.CreatingResourceError{ + ResourceKind: "Deployment", + ResourceName: instance.GetDeploymentMetadata().Name, + ResourceNamespace: instance.GetDeploymentMetadata().Namespace, + } + } + + return nil +} diff --git a/pkg/controllers/v1alpha2/toolkit/code_editor/helpers.go b/pkg/controllers/v1alpha2/toolkit/code_editor/helpers.go new file mode 100644 index 00000000..78b54c04 --- /dev/null +++ b/pkg/controllers/v1alpha2/toolkit/code_editor/helpers.go @@ -0,0 +1,82 @@ +package code_editor + +import ( + "context" + + robotErr "github.com/robolaunch/robot-operator/internal/error" + "github.com/robolaunch/robot-operator/internal/label" + robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func (r *CodeEditorReconciler) reconcileGetInstance(ctx context.Context, meta types.NamespacedName) (*robotv1alpha2.CodeEditor, error) { + instance := &robotv1alpha2.CodeEditor{} + err := r.Get(ctx, meta, instance) + if err != nil { + return &robotv1alpha2.CodeEditor{}, err + } + + return instance, nil +} + +func (r *CodeEditorReconciler) reconcileUpdateInstanceStatus(ctx context.Context, instance *robotv1alpha2.CodeEditor) error { + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + instanceLV := &robotv1alpha2.CodeEditor{} + err := r.Get(ctx, types.NamespacedName{ + Name: instance.Name, + Namespace: instance.Namespace, + }, instanceLV) + + if err == nil { + instance.ResourceVersion = instanceLV.ResourceVersion + } + + err1 := r.Status().Update(ctx, instance) + return err1 + }) +} + +func (r *CodeEditorReconciler) reconcileGetNode(ctx context.Context, instance *robotv1alpha2.CodeEditor) (*corev1.Node, error) { + + tenancyMap := label.GetTenancyMap(instance) + + requirements := []labels.Requirement{} + for k, v := range tenancyMap { + newReq, err := labels.NewRequirement(k, selection.In, []string{v}) + if err != nil { + return nil, err + } + requirements = append(requirements, *newReq) + } + + nodeSelector := labels.NewSelector().Add(requirements...) + + nodes := &corev1.NodeList{} + err := r.List(ctx, nodes, &client.ListOptions{ + LabelSelector: nodeSelector, + }) + if err != nil { + return nil, err + } + + if len(nodes.Items) == 0 { + return nil, &robotErr.NodeNotFoundError{ + ResourceKind: instance.Kind, + ResourceName: instance.Name, + ResourceNamespace: instance.Namespace, + } + } else if len(nodes.Items) > 1 { + return nil, &robotErr.MultipleNodeFoundError{ + ResourceKind: instance.Kind, + ResourceName: instance.Name, + ResourceNamespace: instance.Namespace, + } + } + + return &nodes.Items[0], nil +} diff --git a/pkg/controllers/v1alpha2/toolkit/code_editor/register.go b/pkg/controllers/v1alpha2/toolkit/code_editor/register.go new file mode 100644 index 00000000..3c7815d8 --- /dev/null +++ b/pkg/controllers/v1alpha2/toolkit/code_editor/register.go @@ -0,0 +1,41 @@ +package code_editor + +import ( + robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2" + corev1 "k8s.io/api/core/v1" +) + +func (r *CodeEditorReconciler) registerPVCs(instance *robotv1alpha2.CodeEditor) { + + pvcStatuses := []robotv1alpha2.OwnedPVCStatus{} + + if len(instance.Spec.VolumeClaimTemplates) != len(instance.Status.PVCStatuses) { + for key := range instance.Spec.VolumeClaimTemplates { + pvcStatus := robotv1alpha2.OwnedPVCStatus{ + Resource: robotv1alpha2.OwnedResourceStatus{ + Reference: corev1.ObjectReference{ + Namespace: instance.Namespace, + Name: instance.GetPersistentVolumeClaimMetadata(key).Name, + }, + }, + } + pvcStatuses = append(pvcStatuses, pvcStatus) + } + instance.Status.PVCStatuses = pvcStatuses + } +} + +func (r *CodeEditorReconciler) registerExternalVolumes(instance *robotv1alpha2.CodeEditor) { + + evStatuses := []robotv1alpha2.ExternalVolumeStatus{} + + if len(instance.Spec.ExternalVolumes) != len(instance.Status.ExternalVolumeStatuses) { + for _, ev := range instance.Spec.ExternalVolumes { + evStatus := robotv1alpha2.ExternalVolumeStatus{ + Name: ev.Name, + } + evStatuses = append(evStatuses, evStatus) + } + instance.Status.ExternalVolumeStatuses = evStatuses + } +} diff --git a/pkg/controllers/v1alpha2/toolkit/code_editor/suite_test.go b/pkg/controllers/v1alpha2/toolkit/code_editor/suite_test.go new file mode 100644 index 00000000..410411f0 --- /dev/null +++ b/pkg/controllers/v1alpha2/toolkit/code_editor/suite_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package code_editor + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = robotv1alpha2.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +})