diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c38ead7..e8ea499b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
## [Unreleased]
+
+## [v0.2.7-alpha.6.2] - 2024-03-18
+### Fix
+- **protocol:** switch protocol field from v1.Protocol to string
+
+
## [v0.2.7-alpha.6] - 2024-03-06
### Feat
@@ -11,12 +17,6 @@
- **typo:** fix field reference
-
-## [v0.2.7-alpha.4-udp-test-2] - 2024-03-04
-
-
-## [v0.2.7-alpha.5] - 2024-03-04
-
## [v0.2.7-alpha.4] - 2024-02-15
@@ -440,10 +440,9 @@
- Merge pull request [#24](https://github.com/robolaunch/robot-operator/issues/24) from robolaunch/23-allow-multiple-launches
-[Unreleased]: https://github.com/robolaunch/robot-operator/compare/v0.2.7-alpha.6...HEAD
-[v0.2.7-alpha.6]: https://github.com/robolaunch/robot-operator/compare/v0.2.7-alpha.4-udp-test-2...v0.2.7-alpha.6
-[v0.2.7-alpha.4-udp-test-2]: https://github.com/robolaunch/robot-operator/compare/v0.2.7-alpha.5...v0.2.7-alpha.4-udp-test-2
-[v0.2.7-alpha.5]: https://github.com/robolaunch/robot-operator/compare/v0.2.7-alpha.4...v0.2.7-alpha.5
+[Unreleased]: https://github.com/robolaunch/robot-operator/compare/v0.2.7-alpha.6.2...HEAD
+[v0.2.7-alpha.6.2]: https://github.com/robolaunch/robot-operator/compare/v0.2.7-alpha.6...v0.2.7-alpha.6.2
+[v0.2.7-alpha.6]: https://github.com/robolaunch/robot-operator/compare/v0.2.7-alpha.4...v0.2.7-alpha.6
[v0.2.7-alpha.4]: https://github.com/robolaunch/robot-operator/compare/v0.2.7-alpha.1...v0.2.7-alpha.4
[v0.2.7-alpha.1]: https://github.com/robolaunch/robot-operator/compare/v0.2.6-alpha.19...v0.2.7-alpha.1
[v0.2.6-alpha.19]: https://github.com/robolaunch/robot-operator/compare/v0.2.6-alpha.18...v0.2.6-alpha.19
diff --git a/config/crd/bases/robot.roboscale.io_codeeditors.yaml b/config/crd/bases/robot.roboscale.io_codeeditors.yaml
index 6eed9a65..49126954 100644
--- a/config/crd/bases/robot.roboscale.io_codeeditors.yaml
+++ b/config/crd/bases/robot.roboscale.io_codeeditors.yaml
@@ -1805,6 +1805,9 @@ spec:
- name
type: object
type: array
+ ingress:
+ description: CodeEditor will create an Ingress resource if `true`.
+ type: boolean
port:
default: 9000
description: Port that code editor will use inside the container.
@@ -1813,9 +1816,21 @@ spec:
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
+ serviceType:
+ default: ClusterIP
+ description: Service type of CodeEditor. `ClusterIP` and `NodePort`
+ is supported.
+ enum:
+ - ClusterIP
+ - NodePort
+ type: string
+ tlsSecretName:
+ description: Name of the TLS secret for ingress resource.
+ type: string
+ version:
+ default: 4.22.0
+ description: App version of the code editor.
+ type: string
volumeClaimTemplates:
description: Volume templates for ROS 2 workload. For each volume
template, operator will create a PersistentVolumeClaim that can
@@ -2048,6 +2063,7 @@ spec:
type: array
required:
- port
+ - version
type: object
status:
description: Most recently observed status of the CodeEditor.
@@ -2364,6 +2380,55 @@ spec:
type: string
type: object
type: array
+ ingressStatus:
+ description: Status of CodeEditor Ingress.
+ 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
phase:
description: Phase of CodeEditor. It sums the general status of code
editor.
@@ -2515,6 +2580,68 @@ spec:
type: object
type: object
type: array
+ serviceStatus:
+ description: Status of code editor service.
+ 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
+ urls:
+ additionalProperties:
+ type: string
+ description: Connection URL.
+ type: object
+ type: object
+ workloadUpdateNeeded:
+ description: Field to indicate if the workload should be restarted.
+ type: boolean
type: object
type: object
served: true
diff --git a/config/crd/bases/robot.roboscale.io_discoveryservers.yaml b/config/crd/bases/robot.roboscale.io_discoveryservers.yaml
index 3255dba8..f2887ca1 100644
--- a/config/crd/bases/robot.roboscale.io_discoveryservers.yaml
+++ b/config/crd/bases/robot.roboscale.io_discoveryservers.yaml
@@ -75,7 +75,9 @@ spec:
Server's hostname. Used for getting Server's IP over DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used if `.spec.type`
diff --git a/config/crd/bases/robot.roboscale.io_robotartifacts.yaml b/config/crd/bases/robot.roboscale.io_robotartifacts.yaml
index 5eca87b5..56577c80 100644
--- a/config/crd/bases/robot.roboscale.io_robotartifacts.yaml
+++ b/config/crd/bases/robot.roboscale.io_robotartifacts.yaml
@@ -120,7 +120,9 @@ spec:
DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used
diff --git a/config/crd/bases/robot.roboscale.io_robots.yaml b/config/crd/bases/robot.roboscale.io_robots.yaml
index 7b33c653..c8d326cb 100644
--- a/config/crd/bases/robot.roboscale.io_robots.yaml
+++ b/config/crd/bases/robot.roboscale.io_robots.yaml
@@ -141,7 +141,9 @@ spec:
DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used
diff --git a/config/crd/bases/robot.roboscale.io_ros2workloads.yaml b/config/crd/bases/robot.roboscale.io_ros2workloads.yaml
index 44b0d023..6de29fcf 100644
--- a/config/crd/bases/robot.roboscale.io_ros2workloads.yaml
+++ b/config/crd/bases/robot.roboscale.io_ros2workloads.yaml
@@ -55,7 +55,9 @@ spec:
Server's hostname. Used for getting Server's IP over DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used if
diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml
index 95dfb6ed..fbffece1 100644
--- a/config/manager/kustomization.yaml
+++ b/config/manager/kustomization.yaml
@@ -5,4 +5,4 @@ kind: Kustomization
images:
- name: controller
newName: robolaunchio/robot-controller-manager
- newTag: v0.2.7-alpha.6.1
+ newTag: v0.2.7-alpha.6.3
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 98ceb84a..b6c6e76f 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -53,6 +53,18 @@ rules:
- patch
- update
- watch
+- apiGroups:
+ - ""
+ resources:
+ - events
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
- apiGroups:
- ""
resources:
diff --git a/docs/_coverpage.md b/docs/_coverpage.md
index 04379284..693ee0cb 100644
--- a/docs/_coverpage.md
+++ b/docs/_coverpage.md
@@ -4,6 +4,6 @@
![](https://raw.githubusercontent.com/robolaunch/trademark/main/logos/svg/rocket.svg)
-# Robot Operator 0.2.7-alpha.6.1
+# Robot Operator 0.2.7-alpha.6.3
robolaunch Kubernetes Robot Operator manages lifecycle of ROS 2 based robots and enables defining, deploying and distributing robots declaratively.
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 3c9340e7..bd37821c 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/robolaunch/robot-operator
-go 1.21
+go 1.21.1
require (
github.com/go-logr/logr v1.2.3
@@ -8,6 +8,7 @@ require (
github.com/onsi/ginkgo/v2 v2.6.0
github.com/onsi/gomega v1.24.1
github.com/robolaunch/cosmodrome v0.1.0-alpha.12
+ github.com/robolaunch/platform/server v0.0.0-20240313131851-88d97e7d270b
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.26.1
k8s.io/apimachinery v0.26.1
@@ -36,6 +37,7 @@ require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
+ github.com/gorilla/mux v1.8.1 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
diff --git a/go.sum b/go.sum
index 56e6b39a..58ac6788 100644
--- a/go.sum
+++ b/go.sum
@@ -169,6 +169,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -256,6 +258,8 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/robolaunch/cosmodrome v0.1.0-alpha.12 h1:1ezr5n+7cB/YLv425rc6kkS0iU2ykU9TDNEPQcs8yeQ=
github.com/robolaunch/cosmodrome v0.1.0-alpha.12/go.mod h1:TMDMX9Eir84mHX74QqY+qV0E1NHAnXy67yvIPMvn8ko=
+github.com/robolaunch/platform/server v0.0.0-20240313131851-88d97e7d270b h1:Juw9f0glukHPbPwnCsOJRwyRZbvfAQFC0SfyAgBYWHA=
+github.com/robolaunch/platform/server v0.0.0-20240313131851-88d97e7d270b/go.mod h1:SN3BW8ejEhBHDMmfC1Q20mDltK4l0s88z3S9pVVarZk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
diff --git a/hack/deploy/chart/robot-operator/Chart.yaml b/hack/deploy/chart/robot-operator/Chart.yaml
index b3b19948..066d3d39 100644
--- a/hack/deploy/chart/robot-operator/Chart.yaml
+++ b/hack/deploy/chart/robot-operator/Chart.yaml
@@ -13,9 +13,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 0.2.7-alpha.6.1
+version: 0.2.7-alpha.6.3
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
-appVersion: "v0.2.7-alpha.6.1"
+appVersion: "v0.2.7-alpha.6.3"
diff --git a/hack/deploy/chart/robot-operator/templates/codeeditor-crd.yaml b/hack/deploy/chart/robot-operator/templates/codeeditor-crd.yaml
index 154fe391..c9e2afcc 100644
--- a/hack/deploy/chart/robot-operator/templates/codeeditor-crd.yaml
+++ b/hack/deploy/chart/robot-operator/templates/codeeditor-crd.yaml
@@ -1781,9 +1781,18 @@ spec:
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
+ serviceType:
+ default: ClusterIP
+ description: Service type of CodeEditor. `ClusterIP` and `NodePort`
+ is supported.
+ enum:
+ - ClusterIP
+ - NodePort
+ type: string
+ version:
+ default: 4.22.0
+ description: App version of the code editor.
+ type: string
volumeClaimTemplates:
description: Volume templates for ROS 2 workload. For each volume template,
operator will create a PersistentVolumeClaim that can be mounted to
@@ -2014,6 +2023,7 @@ spec:
type: array
required:
- port
+ - version
type: object
status:
description: Most recently observed status of the CodeEditor.
@@ -2478,6 +2488,65 @@ spec:
type: object
type: object
type: array
+ serviceStatus:
+ description: Status of code editor service.
+ 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
+ urls:
+ additionalProperties:
+ type: string
+ description: Connection URL.
+ type: object
+ type: object
type: object
type: object
served: true
diff --git a/hack/deploy/chart/robot-operator/templates/discoveryserver-crd.yaml b/hack/deploy/chart/robot-operator/templates/discoveryserver-crd.yaml
index e5768233..887e4b9e 100644
--- a/hack/deploy/chart/robot-operator/templates/discoveryserver-crd.yaml
+++ b/hack/deploy/chart/robot-operator/templates/discoveryserver-crd.yaml
@@ -75,7 +75,9 @@ spec:
hostname. Used for getting Server's IP over DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used if `.spec.type`
diff --git a/hack/deploy/chart/robot-operator/templates/metrics-service.yaml b/hack/deploy/chart/robot-operator/templates/metrics-service.yaml
index b106621e..3f0f5bfc 100644
--- a/hack/deploy/chart/robot-operator/templates/metrics-service.yaml
+++ b/hack/deploy/chart/robot-operator/templates/metrics-service.yaml
@@ -14,4 +14,4 @@ spec:
control-plane: controller-manager
{{- include "robot-operator.selectorLabels" . | nindent 4 }}
ports:
- {{- .Values.metricsService.ports | toYaml | nindent 2 }}
\ No newline at end of file
+ {{- .Values.metricsService.ports | toYaml | nindent 2 -}}
\ No newline at end of file
diff --git a/hack/deploy/chart/robot-operator/templates/robot-crd.yaml b/hack/deploy/chart/robot-operator/templates/robot-crd.yaml
index 4649b433..5c9605fe 100644
--- a/hack/deploy/chart/robot-operator/templates/robot-crd.yaml
+++ b/hack/deploy/chart/robot-operator/templates/robot-crd.yaml
@@ -153,7 +153,9 @@ spec:
DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used
diff --git a/hack/deploy/chart/robot-operator/templates/robotartifact-crd.yaml b/hack/deploy/chart/robot-operator/templates/robotartifact-crd.yaml
index ec9ca6f5..43102fbf 100644
--- a/hack/deploy/chart/robot-operator/templates/robotartifact-crd.yaml
+++ b/hack/deploy/chart/robot-operator/templates/robotartifact-crd.yaml
@@ -120,7 +120,9 @@ spec:
DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used
diff --git a/hack/deploy/chart/robot-operator/templates/ros2workload-crd.yaml b/hack/deploy/chart/robot-operator/templates/ros2workload-crd.yaml
index 7998a706..c475ab9c 100644
--- a/hack/deploy/chart/robot-operator/templates/ros2workload-crd.yaml
+++ b/hack/deploy/chart/robot-operator/templates/ros2workload-crd.yaml
@@ -55,7 +55,9 @@ spec:
Server's hostname. Used for getting Server's IP over DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used if `.spec.type`
diff --git a/hack/deploy/chart/robot-operator/templates/webhook-service.yaml b/hack/deploy/chart/robot-operator/templates/webhook-service.yaml
index 61ab9ec1..6ffc253b 100644
--- a/hack/deploy/chart/robot-operator/templates/webhook-service.yaml
+++ b/hack/deploy/chart/robot-operator/templates/webhook-service.yaml
@@ -13,4 +13,4 @@ spec:
control-plane: controller-manager
{{- include "robot-operator.selectorLabels" . | nindent 4 }}
ports:
- {{- .Values.webhookService.ports | toYaml | nindent 2 }}
\ No newline at end of file
+ {{- .Values.webhookService.ports | toYaml | nindent 2 -}}
\ No newline at end of file
diff --git a/hack/deploy/chart/robot-operator/values.yaml b/hack/deploy/chart/robot-operator/values.yaml
index 6ae8eda7..ca2bca55 100644
--- a/hack/deploy/chart/robot-operator/values.yaml
+++ b/hack/deploy/chart/robot-operator/values.yaml
@@ -32,7 +32,7 @@ controllerManager:
- ALL
image:
repository: robolaunchio/robot-controller-manager
- tag: v0.2.7-alpha.6.1
+ tag: v0.2.7-alpha.6.3
resources:
limits:
cpu: 500m
diff --git a/hack/deploy/manifests/robot_operator.yaml b/hack/deploy/manifests/robot_operator.yaml
index 9f3158f0..0b7d5fdd 100644
--- a/hack/deploy/manifests/robot_operator.yaml
+++ b/hack/deploy/manifests/robot_operator.yaml
@@ -1518,9 +1518,17 @@ spec:
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
+ serviceType:
+ default: ClusterIP
+ description: Service type of CodeEditor. `ClusterIP` and `NodePort` is supported.
+ enum:
+ - ClusterIP
+ - NodePort
+ type: string
+ version:
+ default: 4.22.0
+ description: App version of the code editor.
+ type: string
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:
@@ -1657,6 +1665,7 @@ spec:
type: array
required:
- port
+ - version
type: object
status:
description: Most recently observed status of the CodeEditor.
@@ -2027,6 +2036,53 @@ spec:
type: object
type: object
type: array
+ serviceStatus:
+ description: Status of code editor service.
+ 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
+ urls:
+ additionalProperties:
+ type: string
+ description: Connection URL.
+ type: object
+ type: object
type: object
type: object
served: true
@@ -2100,7 +2156,9 @@ spec:
description: If instance type is `Server`, it can be an arbitrary value. If instance type is `Client`, it should be the same with Server's hostname. Used for getting Server's IP over DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used if `.spec.type` is `Client`. Referenced object can be previously provisioned in another cluster. In that case, cluster's name can be specified in `.spec.cluster` field.
@@ -4015,7 +4073,9 @@ spec:
description: If instance type is `Server`, it can be an arbitrary value. If instance type is `Client`, it should be the same with Server's hostname. Used for getting Server's IP over DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used if `.spec.type` is `Client`. Referenced object can be previously provisioned in another cluster. In that case, cluster's name can be specified in `.spec.cluster` field.
@@ -5362,7 +5422,9 @@ spec:
description: If instance type is `Server`, it can be an arbitrary value. If instance type is `Client`, it should be the same with Server's hostname. Used for getting Server's IP over DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used if `.spec.type` is `Client`. Referenced object can be previously provisioned in another cluster. In that case, cluster's name can be specified in `.spec.cluster` field.
@@ -8023,7 +8085,9 @@ spec:
description: If instance type is `Server`, it can be an arbitrary value. If instance type is `Client`, it should be the same with Server's hostname. Used for getting Server's IP over DNS.
type: string
protocol:
- default: TCP
+ enum:
+ - TCP
+ - UDP
type: string
reference:
description: Reference to the `Server` instance. It is used if `.spec.type` is `Client`. Referenced object can be previously provisioned in another cluster. In that case, cluster's name can be specified in `.spec.cluster` field.
@@ -11259,7 +11323,7 @@ spec:
- --leader-elect
command:
- /manager
- image: robolaunchio/robot-controller-manager:v0.2.7-alpha.6.1
+ image: robolaunchio/robot-controller-manager:v0.2.7-alpha.6.3
livenessProbe:
httpGet:
path: /healthz
diff --git a/internal/configure/v1alpha2/configure.go b/internal/configure/v1alpha2/configure.go
index 499baa90..f20c8dd3 100644
--- a/internal/configure/v1alpha2/configure.go
+++ b/internal/configure/v1alpha2/configure.go
@@ -1,3 +1,4 @@
package configure
type PodSpecConfigInjector struct{}
+type ServiceSpecConfigInjector struct{}
diff --git a/internal/configure/v1alpha2/remote.go b/internal/configure/v1alpha2/remote.go
new file mode 100644
index 00000000..6328a5fb
--- /dev/null
+++ b/internal/configure/v1alpha2/remote.go
@@ -0,0 +1,22 @@
+package configure
+
+import (
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func (cfg *PodSpecConfigInjector) InjectRemoteConfigurations(podSpec *corev1.PodSpec, obj metav1.Object) *corev1.PodSpec {
+
+ podSpec.Hostname = obj.GetName()
+ podSpec.Subdomain = obj.GetName()
+
+ return podSpec
+}
+
+func (cfg *ServiceSpecConfigInjector) InjectRemoteConfigurations(serviceSpec *corev1.ServiceSpec) *corev1.ServiceSpec {
+
+ serviceSpec.Type = ""
+ serviceSpec.ClusterIP = "None"
+
+ return serviceSpec
+}
diff --git a/internal/label/image.go b/internal/label/image.go
new file mode 100644
index 00000000..324e25b6
--- /dev/null
+++ b/internal/label/image.go
@@ -0,0 +1,37 @@
+package label
+
+import (
+ "github.com/robolaunch/robot-operator/internal"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type ImageMeta struct {
+ Registry string
+ User string
+ Repository string
+ Tag string
+}
+
+func GetImageMeta(obj metav1.Object) *ImageMeta {
+
+ imageMeta := &ImageMeta{}
+ labels := obj.GetLabels()
+
+ if registry, ok := labels[internal.IMAGE_REGISTRY_LABEL_KEY]; ok {
+ imageMeta.Registry = registry
+ }
+
+ if user, ok := labels[internal.IMAGE_USER_LABEL_KEY]; ok {
+ imageMeta.User = user
+ }
+
+ if repository, ok := labels[internal.IMAGE_REPOSITORY_LABEL_KEY]; ok {
+ imageMeta.Repository = repository
+ }
+
+ if tag, ok := labels[internal.IMAGE_TAG_LABEL_KEY]; ok {
+ imageMeta.Tag = tag
+ }
+
+ return imageMeta
+}
diff --git a/internal/label/instance.go b/internal/label/instance.go
new file mode 100644
index 00000000..30b11833
--- /dev/null
+++ b/internal/label/instance.go
@@ -0,0 +1,26 @@
+package label
+
+import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+type InstanceType string
+
+const (
+ InstanceTypeCloudInstance InstanceType = "CloudInstance"
+ InstanceTypePhysicalInstance InstanceType = "PhysicalInstance"
+)
+
+func GetInstanceType(obj metav1.Object) InstanceType {
+ tenancy := GetTenancy(obj)
+ if tenancy.PhysicalInstance == "" {
+ return InstanceTypeCloudInstance
+ }
+ return InstanceTypePhysicalInstance
+}
+
+func GetClusterName(obj metav1.Object) string {
+ tenancy := GetTenancy(obj)
+ if tenancy.PhysicalInstance == "" {
+ return tenancy.CloudInstance
+ }
+ return tenancy.PhysicalInstance
+}
diff --git a/internal/label/platform.go b/internal/label/platform.go
new file mode 100644
index 00000000..577441cc
--- /dev/null
+++ b/internal/label/platform.go
@@ -0,0 +1,26 @@
+package label
+
+import (
+ "github.com/robolaunch/robot-operator/internal"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type PlatformMeta struct {
+ Version string
+ Domain string
+}
+
+func GetPlatformMeta(obj metav1.Object) *PlatformMeta {
+ platformMeta := &PlatformMeta{}
+ labels := obj.GetLabels()
+
+ if version, ok := labels[internal.PLATFORM_VERSION_LABEL_KEY]; ok {
+ platformMeta.Version = version
+ }
+
+ if domain, ok := labels[internal.DOMAIN_LABEL_KEY]; ok {
+ platformMeta.Domain = domain
+ }
+
+ return platformMeta
+}
diff --git a/internal/label/tenancy.go b/internal/label/tenancy.go
index 257796ae..88a84ada 100644
--- a/internal/label/tenancy.go
+++ b/internal/label/tenancy.go
@@ -12,12 +12,6 @@ type Tenancy struct {
CloudInstance string
CloudInstanceAlias string
PhysicalInstance string
- Domain string
-}
-
-type Timezone struct {
- Continent string
- City string
}
func GetTenancy(obj metav1.Object) *Tenancy {
@@ -48,29 +42,9 @@ func GetTenancy(obj metav1.Object) *Tenancy {
tenancy.PhysicalInstance = physicalInstance
}
- if domain, ok := labels[internal.DOMAIN_LABEL_KEY]; ok {
- tenancy.Domain = domain
- }
-
return tenancy
}
-func GetTimezone(obj metav1.Object) *Timezone {
-
- timezone := &Timezone{}
- labels := obj.GetLabels()
-
- if continent, ok := labels[internal.TZ_CONTINENT_LABEL_KEY]; ok {
- timezone.Continent = continent
- }
-
- if city, ok := labels[internal.TZ_CITY_LABEL_KEY]; ok {
- timezone.City = city
- }
-
- return timezone
-}
-
func GetTenancyMap(obj metav1.Object) map[string]string {
labels := obj.GetLabels()
tenancyMap := make(map[string]string)
@@ -131,26 +105,3 @@ func GetTenancyMapFromTenancy(tenancy Tenancy) map[string]string {
return tenancyMap
}
-
-type InstanceType string
-
-const (
- InstanceTypeCloudInstance InstanceType = "CloudInstance"
- InstanceTypePhysicalInstance InstanceType = "PhysicalInstance"
-)
-
-func GetInstanceType(obj metav1.Object) InstanceType {
- tenancy := GetTenancy(obj)
- if tenancy.PhysicalInstance == "" {
- return InstanceTypeCloudInstance
- }
- return InstanceTypePhysicalInstance
-}
-
-func GetClusterName(obj metav1.Object) string {
- tenancy := GetTenancy(obj)
- if tenancy.PhysicalInstance == "" {
- return tenancy.CloudInstance
- }
- return tenancy.PhysicalInstance
-}
diff --git a/internal/label/timezone.go b/internal/label/timezone.go
new file mode 100644
index 00000000..ec514b01
--- /dev/null
+++ b/internal/label/timezone.go
@@ -0,0 +1,27 @@
+package label
+
+import (
+ "github.com/robolaunch/robot-operator/internal"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type Timezone struct {
+ Continent string
+ City string
+}
+
+func GetTimezone(obj metav1.Object) *Timezone {
+
+ timezone := &Timezone{}
+ labels := obj.GetLabels()
+
+ if continent, ok := labels[internal.TZ_CONTINENT_LABEL_KEY]; ok {
+ timezone.Continent = continent
+ }
+
+ if city, ok := labels[internal.TZ_CITY_LABEL_KEY]; ok {
+ timezone.City = city
+ }
+
+ return timezone
+}
diff --git a/internal/node/image.go b/internal/node/image.go
index 1afaef43..f19ecf99 100644
--- a/internal/node/image.go
+++ b/internal/node/image.go
@@ -9,6 +9,7 @@ import (
cosmodrome "github.com/robolaunch/cosmodrome/pkg/api"
"github.com/robolaunch/robot-operator/internal"
+ "github.com/robolaunch/robot-operator/internal/label"
robotv1alpha1 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha1"
robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2"
corev1 "k8s.io/api/core/v1"
@@ -56,10 +57,10 @@ type ReadyRobotProperties struct {
func GetReadyRobotProperties(robot robotv1alpha1.Robot) ReadyRobotProperties {
labels := robot.GetLabels()
- if registry, hasRegistry := labels[internal.ROBOT_IMAGE_REGISTRY_LABEL_KEY]; hasRegistry {
- if user, hasUser := labels[internal.ROBOT_IMAGE_USER_LABEL_KEY]; hasUser {
- if repository, hasRepository := labels[internal.ROBOT_IMAGE_REPOSITORY_LABEL_KEY]; hasRepository {
- if tag, hasTag := labels[internal.ROBOT_IMAGE_TAG_LABEL_KEY]; hasTag {
+ if registry, hasRegistry := labels[internal.IMAGE_REGISTRY_LABEL_KEY]; hasRegistry {
+ if user, hasUser := labels[internal.IMAGE_USER_LABEL_KEY]; hasUser {
+ if repository, hasRepository := labels[internal.IMAGE_REPOSITORY_LABEL_KEY]; hasRepository {
+ if tag, hasTag := labels[internal.IMAGE_TAG_LABEL_KEY]; hasTag {
return ReadyRobotProperties{
Enabled: true,
Image: registry + "/" + user + "/" + repository + ":" + tag,
@@ -95,15 +96,16 @@ func GetImageForRobot(ctx context.Context, r client.Client, node corev1.Node, ro
} else {
- platformVersion := GetPlatformVersion(node)
- imageProps, err := getImagePropsForRobot(ctx, r, platformVersion, getDistroStr(robot.Spec.RobotConfig.Distributions))
+ platformMeta := label.GetPlatformMeta(&node)
+
+ imageProps, err := getImagePropsForRobot(ctx, r, platformMeta.Version, getDistroStr(robot.Spec.RobotConfig.Distributions))
if err != nil {
return "", err
}
- registry, hasRegistry := robot.Labels[internal.ROBOT_IMAGE_REGISTRY_LABEL_KEY]
+ registry, hasRegistry := robot.Labels[internal.IMAGE_REGISTRY_LABEL_KEY]
if !hasRegistry {
- return "", errors.New("registry is not found in label with key " + internal.ROBOT_IMAGE_REGISTRY_LABEL_KEY)
+ return "", errors.New("registry is not found in label with key " + internal.IMAGE_REGISTRY_LABEL_KEY)
}
organization := "robolaunchio"
@@ -125,15 +127,16 @@ func GetImageForBridge(ctx context.Context, r client.Client, node corev1.Node, r
var imageBuilder strings.Builder
var tagBuilder strings.Builder
- platformVersion := GetPlatformVersion(node)
- imageProps, err := getImagePropsForRobot(ctx, r, platformVersion, getDistroStr(robot.Spec.RobotConfig.Distributions))
+ platformMeta := label.GetPlatformMeta(&node)
+
+ imageProps, err := getImagePropsForRobot(ctx, r, platformMeta.Version, getDistroStr(robot.Spec.RobotConfig.Distributions))
if err != nil {
return "", err
}
- registry, hasRegistry := robot.Labels[internal.ROBOT_IMAGE_REGISTRY_LABEL_KEY]
+ registry, hasRegistry := robot.Labels[internal.IMAGE_REGISTRY_LABEL_KEY]
if !hasRegistry {
- return "", errors.New("registry is not found in label with key " + internal.ROBOT_IMAGE_REGISTRY_LABEL_KEY)
+ return "", errors.New("registry is not found in label with key " + internal.IMAGE_REGISTRY_LABEL_KEY)
}
organization := "robolaunchio"
@@ -151,15 +154,16 @@ func GetBridgeImage(ctx context.Context, r client.Client, node corev1.Node, ros2
var imageBuilder strings.Builder
var tagBuilder strings.Builder
- platformVersion := GetPlatformVersion(node)
- imageProps, err := getImagePropsForRobot(ctx, r, platformVersion, getDistroStr([]robotv1alpha1.ROSDistro{ros2Bridge.Spec.Distro}))
+ platformMeta := label.GetPlatformMeta(&node)
+
+ imageProps, err := getImagePropsForRobot(ctx, r, platformMeta.Version, getDistroStr([]robotv1alpha1.ROSDistro{ros2Bridge.Spec.Distro}))
if err != nil {
return "", err
}
- registry, hasRegistry := ros2Bridge.Labels[internal.ROBOT_IMAGE_REGISTRY_LABEL_KEY]
+ registry, hasRegistry := ros2Bridge.Labels[internal.IMAGE_REGISTRY_LABEL_KEY]
if !hasRegistry {
- return "", errors.New("registry is not found in label with key " + internal.ROBOT_IMAGE_REGISTRY_LABEL_KEY)
+ return "", errors.New("registry is not found in label with key " + internal.IMAGE_REGISTRY_LABEL_KEY)
}
organization := "robolaunchio"
@@ -185,15 +189,16 @@ func GetImageForEnvironment(ctx context.Context, r client.Client, node corev1.No
} else {
- platformVersion := GetPlatformVersion(node)
- imageProps, err := getImagePropsForEnvironment(ctx, r, platformVersion)
+ platformMeta := label.GetPlatformMeta(&node)
+
+ imageProps, err := getImagePropsForEnvironment(ctx, r, platformMeta.Version)
if err != nil {
return "", err
}
- registry, hasRegistry := robot.Labels[internal.ROBOT_IMAGE_REGISTRY_LABEL_KEY]
+ registry, hasRegistry := robot.Labels[internal.IMAGE_REGISTRY_LABEL_KEY]
if !hasRegistry {
- return "", errors.New("registry is not found in label with key " + internal.ROBOT_IMAGE_REGISTRY_LABEL_KEY)
+ return "", errors.New("registry is not found in label with key " + internal.IMAGE_REGISTRY_LABEL_KEY)
}
organization := imageProps.Organization
diff --git a/internal/node/node.go b/internal/node/node.go
index 1e6011ec..b176654a 100644
--- a/internal/node/node.go
+++ b/internal/node/node.go
@@ -1,7 +1,6 @@
package node
import (
- "github.com/robolaunch/robot-operator/internal"
corev1 "k8s.io/api/core/v1"
)
@@ -16,10 +15,3 @@ func IsK3s(node corev1.Node) bool {
}
return false
}
-
-func GetPlatformVersion(node corev1.Node) string {
- if platformVersion, ok := node.Labels[internal.PLATFORM_VERSION_LABEL_KEY]; ok {
- return platformVersion
- }
- return ""
-}
diff --git a/internal/platform/environment.go b/internal/platform/environment.go
new file mode 100644
index 00000000..29c52098
--- /dev/null
+++ b/internal/platform/environment.go
@@ -0,0 +1,50 @@
+package platform
+
+import (
+ "errors"
+ "net/url"
+
+ platformServer "github.com/robolaunch/platform/server/pkg"
+ platformArtifact "github.com/robolaunch/platform/server/pkg/models"
+ "github.com/robolaunch/robot-operator/internal"
+)
+
+func GetVersionedPlatformEnvironments(version string) (platformArtifact.VersionedPlatformImages, error) {
+
+ environmentsResponse := platformServer.GetVersionedPlatformImagesResponse(
+ map[string]string{
+ "version": version,
+ },
+ url.Values{
+ "url": []string{
+ internal.DEFAULT_ENVIRONMENTS_URL,
+ },
+ },
+ )
+
+ if !environmentsResponse.Success {
+ return platformArtifact.VersionedPlatformImages{}, errors.New(environmentsResponse.Message)
+ }
+
+ return platformArtifact.VersionedPlatformImages{}, nil
+}
+
+func GetVersionedPlatformToolEnvironments(version string) (platformArtifact.VersionedPlatformImages, error) {
+
+ environmentsResponse := platformServer.GetVersionedPlatformImagesResponse(
+ map[string]string{
+ "version": version,
+ },
+ url.Values{
+ "url": []string{
+ internal.DEFAULT_TOOLS_ENVIRONMENTS_URL,
+ },
+ },
+ )
+
+ if !environmentsResponse.Success {
+ return platformArtifact.VersionedPlatformImages{}, errors.New(environmentsResponse.Message)
+ }
+
+ return *environmentsResponse.Response, nil
+}
diff --git a/internal/platform/image.go b/internal/platform/image.go
new file mode 100644
index 00000000..547bc763
--- /dev/null
+++ b/internal/platform/image.go
@@ -0,0 +1,40 @@
+package platform
+
+import (
+ "errors"
+ "reflect"
+
+ "github.com/robolaunch/platform/server/pkg/models"
+ "github.com/robolaunch/robot-operator/internal/label"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func GetToolsImage(obj metav1.Object, platformVersion string, application string, appVersion string) (string, error) {
+
+ imageMeta := label.GetImageMeta(obj)
+
+ vpi, err := GetVersionedPlatformToolEnvironments(platformVersion)
+ if err != nil {
+ return "", err
+ }
+
+ selectedEnvironment := models.Environment{}
+
+ if domain, ok := vpi.Domains["tools"]; ok {
+ for _, environment := range domain.Environments {
+ if environment.Application.Name == application && environment.Application.Version == appVersion {
+ selectedEnvironment = environment
+ }
+ }
+ } else {
+ return "", errors.New("cannot find tools domain")
+ }
+
+ if reflect.DeepEqual(selectedEnvironment, models.Environment{}) {
+ return "", errors.New("cannot find application '" + application + "' with version '" + appVersion + "'")
+ }
+
+ imageName := imageMeta.Registry + "/" + vpi.Organization + "/" + application + ":" + selectedEnvironment.Application.Version + "-" + selectedEnvironment.Base.Version
+
+ return imageName, nil
+}
diff --git a/internal/platform/platform.go b/internal/platform/platform.go
new file mode 100644
index 00000000..4f696ebc
--- /dev/null
+++ b/internal/platform/platform.go
@@ -0,0 +1,27 @@
+package platform
+
+import (
+ "errors"
+ "net/url"
+
+ platformServer "github.com/robolaunch/platform/server/pkg"
+ platformArtifact "github.com/robolaunch/platform/server/pkg/models"
+ "github.com/robolaunch/robot-operator/internal"
+)
+
+func GetPlatform() (platformArtifact.Robolaunch, error) {
+
+ platformResponse := platformServer.GetPlatformResponse(
+ url.Values{
+ "url": []string{
+ internal.DEFAULT_PLATFORM_URL,
+ },
+ },
+ )
+
+ if !platformResponse.Success {
+ return platformArtifact.Robolaunch{}, errors.New(platformResponse.Message)
+ }
+
+ return *platformResponse.Response, nil
+}
diff --git a/internal/resources/v1alpha1/discovery_server.go b/internal/resources/v1alpha1/discovery_server.go
index 98cdfe25..2cd965aa 100644
--- a/internal/resources/v1alpha1/discovery_server.go
+++ b/internal/resources/v1alpha1/discovery_server.go
@@ -37,7 +37,7 @@ func GetDiscoveryServerPod(discoveryServer *robotv1alpha1.DiscoveryServer, podNa
{
Name: discoveryServerPortName,
ContainerPort: int32(discoveryServerPortNumber),
- Protocol: discoveryServer.Spec.Protocol,
+ Protocol: corev1.Protocol(discoveryServer.Spec.Protocol),
},
},
},
@@ -79,7 +79,7 @@ func GetDiscoveryServerService(discoveryServer *robotv1alpha1.DiscoveryServer, s
TargetPort: intstr.IntOrString{
IntVal: int32(discoveryServerPortNumber),
},
- Protocol: discoveryServer.Spec.Protocol,
+ Protocol: corev1.Protocol(discoveryServer.Spec.Protocol),
Name: discoveryServerPortName,
},
},
diff --git a/internal/resources/v1alpha2/code_editor.go b/internal/resources/v1alpha2/code_editor.go
index 06756a53..2604e8e8 100644
--- a/internal/resources/v1alpha2/code_editor.go
+++ b/internal/resources/v1alpha2/code_editor.go
@@ -1,19 +1,31 @@
package v1alpha2_resources
import (
+ "fmt"
+ "path/filepath"
+ "strconv"
+ "strings"
+
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
+ networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/util/intstr"
"github.com/robolaunch/robot-operator/internal"
configure "github.com/robolaunch/robot-operator/internal/configure/v1alpha2"
+ "github.com/robolaunch/robot-operator/internal/label"
+ "github.com/robolaunch/robot-operator/internal/platform"
+ robotv1alpha1 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha1"
robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2"
)
-const (
- CODE_EDITOR_PORT_NAME = "code-server"
-)
+func getCodeEditorSelector(codeEditor robotv1alpha2.CodeEditor) map[string]string {
+ return map[string]string{
+ internal.CODE_EDITOR_CONTAINER_SELECTOR_LABEL_KEY: codeEditor.Name,
+ }
+}
func GetCodeEditorPersistentVolumeClaim(codeEditor *robotv1alpha2.CodeEditor, pvcNamespacedName *types.NamespacedName, key int) *corev1.PersistentVolumeClaim {
@@ -32,17 +44,34 @@ func GetCodeEditorPersistentVolumeClaim(codeEditor *robotv1alpha2.CodeEditor, pv
func GetCodeEditorDeployment(codeEditor *robotv1alpha2.CodeEditor, deploymentNamespacedName *types.NamespacedName, node corev1.Node) *appsv1.Deployment {
+ platformMeta := label.GetPlatformMeta(&node)
+
cfg := configure.PodSpecConfigInjector{}
+ image, err := platform.GetToolsImage(codeEditor, platformMeta.Version, internal.CODE_EDITOR_APP_NAME, codeEditor.Spec.Version)
+ if err != nil {
+ return nil
+ }
+
+ var cmdBuilder strings.Builder
+ cmdBuilder.WriteString("supervisord -c " + filepath.Join("/etc", "robolaunch", "services", "code-server.conf"))
+
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"},
+ Name: internal.CODE_EDITOR_APP_NAME,
+ Image: image,
+ Command: internal.Bash(cmdBuilder.String()),
+ Env: []corev1.EnvVar{
+ internal.Env("CODE_SERVER_PORT", strconv.FormatInt(int64(codeEditor.Spec.Port), 10)),
+ internal.Env("FILE_BROWSER_PORT", strconv.Itoa(internal.FILE_BROWSER_PORT)),
+ internal.Env("FILE_BROWSER_SERVICE", "code-server"),
+ internal.Env("FILE_BROWSER_BASE_URL", robotv1alpha1.GetServicePath(codeEditor, "/"+internal.FILE_BROWSER_PORT_NAME)),
+ internal.Env("TERM", "xterm-256color"),
+ },
Ports: []corev1.ContainerPort{
{
- Name: CODE_EDITOR_PORT_NAME,
+ Name: internal.CODE_EDITOR_PORT_NAME,
ContainerPort: codeEditor.Spec.Port,
Protocol: corev1.ProtocolTCP,
},
@@ -65,8 +94,8 @@ func GetCodeEditorDeployment(codeEditor *robotv1alpha2.CodeEditor, deploymentNam
cfg.InjectVolumeConfiguration(&podSpec, codeEditor.Status.PVCStatuses)
cfg.InjectExternalVolumeConfiguration(&podSpec, codeEditor.Status.ExternalVolumeStatuses)
- if !codeEditor.Spec.Root {
- cfg.InjectLinuxUserAndGroup(&podSpec)
+ if codeEditor.Spec.Remote {
+ cfg.InjectRemoteConfigurations(&podSpec, codeEditor)
}
deployment := appsv1.Deployment{
@@ -77,15 +106,11 @@ func GetCodeEditorDeployment(codeEditor *robotv1alpha2.CodeEditor, deploymentNam
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
- MatchLabels: map[string]string{
- internal.CODE_EDITOR_CONTAINER_SELECTOR_LABEL_KEY: "code-editor",
- },
+ MatchLabels: getCodeEditorSelector(*codeEditor),
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
- Labels: map[string]string{
- internal.CODE_EDITOR_CONTAINER_SELECTOR_LABEL_KEY: "code-editor",
- },
+ Labels: getCodeEditorSelector(*codeEditor),
},
Spec: podSpec,
},
@@ -94,3 +119,125 @@ func GetCodeEditorDeployment(codeEditor *robotv1alpha2.CodeEditor, deploymentNam
return &deployment
}
+
+func GetCodeEditorService(codeEditor *robotv1alpha2.CodeEditor, svcNamespacedName *types.NamespacedName) *corev1.Service {
+
+ cfg := configure.ServiceSpecConfigInjector{}
+
+ serviceSpec := corev1.ServiceSpec{
+ Type: codeEditor.Spec.ServiceType,
+ Selector: getCodeEditorSelector(*codeEditor),
+ Ports: []corev1.ServicePort{
+ {
+ Name: internal.CODE_EDITOR_PORT_NAME,
+ Port: codeEditor.Spec.Port,
+ TargetPort: intstr.IntOrString{
+ IntVal: codeEditor.Spec.Port,
+ },
+ Protocol: corev1.ProtocolTCP,
+ },
+ {
+ Name: internal.FILE_BROWSER_PORT_NAME,
+ Port: internal.FILE_BROWSER_PORT,
+ TargetPort: intstr.IntOrString{
+ IntVal: internal.FILE_BROWSER_PORT,
+ },
+ Protocol: corev1.ProtocolTCP,
+ },
+ },
+ }
+
+ if codeEditor.Spec.Remote {
+ cfg.InjectRemoteConfigurations(&serviceSpec)
+ }
+
+ service := corev1.Service{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: svcNamespacedName.Name,
+ Namespace: svcNamespacedName.Namespace,
+ Labels: codeEditor.Labels,
+ },
+ Spec: serviceSpec,
+ }
+
+ return &service
+}
+
+func GetCodeEditorIngress(codeEditor *robotv1alpha2.CodeEditor, ingressNamespacedName *types.NamespacedName) *networkingv1.Ingress {
+
+ tenancy := label.GetTenancy(codeEditor)
+ platformMeta := label.GetPlatformMeta(codeEditor)
+
+ annotations := map[string]string{
+ internal.INGRESS_AUTH_URL_KEY: fmt.Sprintf(internal.INGRESS_AUTH_URL_VAL, tenancy.Team, platformMeta.Domain),
+ internal.INGRESS_AUTH_SIGNIN_KEY: fmt.Sprintf(internal.INGRESS_AUTH_SIGNIN_VAL, tenancy.Team, platformMeta.Domain),
+ internal.INGRESS_AUTH_RESPONSE_HEADERS_KEY: internal.INGRESS_AUTH_RESPONSE_HEADERS_VAL,
+ internal.INGRESS_CONFIGURATION_SNIPPET_KEY: internal.INGRESS_IDE_CONFIGURATION_SNIPPET_VAL,
+ internal.INGRESS_CERT_MANAGER_KEY: internal.INGRESS_CERT_MANAGER_VAL,
+ internal.INGRESS_NGINX_PROXY_BUFFER_SIZE_KEY: internal.INGRESS_NGINX_PROXY_BUFFER_SIZE_VAL,
+ internal.INGRESS_NGINX_REWRITE_TARGET_KEY: internal.INGRESS_NGINX_REWRITE_TARGET_VAL,
+ internal.INGRESS_PROXY_READ_TIMEOUT_KEY: internal.INGRESS_PROXY_READ_TIMEOUT_VAL,
+ }
+
+ pathTypePrefix := networkingv1.PathTypePrefix
+ ingressClassNameNginx := "nginx"
+
+ ingressSpec := networkingv1.IngressSpec{
+ TLS: []networkingv1.IngressTLS{
+ {
+ Hosts: []string{
+ tenancy.Team + "." + platformMeta.Domain,
+ },
+ SecretName: codeEditor.Spec.TLSSecretName,
+ },
+ },
+ Rules: []networkingv1.IngressRule{
+ {
+ Host: tenancy.Team + "." + platformMeta.Domain,
+ IngressRuleValue: networkingv1.IngressRuleValue{
+ HTTP: &networkingv1.HTTPIngressRuleValue{
+ Paths: []networkingv1.HTTPIngressPath{
+ {
+ Path: robotv1alpha1.GetServicePath(codeEditor, "/"+internal.CODE_EDITOR_APP_NAME) + "(/|$)(.*)",
+ PathType: &pathTypePrefix,
+ Backend: networkingv1.IngressBackend{
+ Service: &networkingv1.IngressServiceBackend{
+ Name: codeEditor.GetServiceMetadata().Name,
+ Port: networkingv1.ServiceBackendPort{
+ Number: codeEditor.Spec.Port,
+ },
+ },
+ },
+ },
+ {
+ Path: robotv1alpha1.GetServicePath(codeEditor, "/"+internal.FILE_BROWSER_PORT_NAME) + "(/|$)(.*)",
+ PathType: &pathTypePrefix,
+ Backend: networkingv1.IngressBackend{
+ Service: &networkingv1.IngressServiceBackend{
+ Name: codeEditor.GetServiceMetadata().Name,
+ Port: networkingv1.ServiceBackendPort{
+ Number: internal.FILE_BROWSER_PORT,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ IngressClassName: &ingressClassNameNginx,
+ }
+
+ ingress := &networkingv1.Ingress{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: ingressNamespacedName.Name,
+ Namespace: ingressNamespacedName.Namespace,
+ Labels: codeEditor.Labels,
+ Annotations: annotations,
+ },
+ Spec: ingressSpec,
+ }
+
+ return ingress
+}
diff --git a/internal/resources/v1alpha2/ros2_bridge.go b/internal/resources/v1alpha2/ros2_bridge.go
index 1da4807d..278229ab 100644
--- a/internal/resources/v1alpha2/ros2_bridge.go
+++ b/internal/resources/v1alpha2/ros2_bridge.go
@@ -97,10 +97,11 @@ func GetROS2BridgeService(rosbridge *robotv1alpha2.ROS2Bridge, svcNamespacedName
func GetROS2BridgeIngress(ros2Bridge *robotv1alpha2.ROS2Bridge, ingressNamespacedName *types.NamespacedName) *networkingv1.Ingress {
tenancy := label.GetTenancy(ros2Bridge)
+ platformMeta := label.GetPlatformMeta(ros2Bridge)
annotations := map[string]string{
- internal.INGRESS_AUTH_URL_KEY: fmt.Sprintf(internal.INGRESS_AUTH_URL_VAL, tenancy.Team, tenancy.Domain),
- internal.INGRESS_AUTH_SIGNIN_KEY: fmt.Sprintf(internal.INGRESS_AUTH_SIGNIN_VAL, tenancy.Team, tenancy.Domain),
+ internal.INGRESS_AUTH_URL_KEY: fmt.Sprintf(internal.INGRESS_AUTH_URL_VAL, tenancy.Team, platformMeta.Domain),
+ internal.INGRESS_AUTH_SIGNIN_KEY: fmt.Sprintf(internal.INGRESS_AUTH_SIGNIN_VAL, tenancy.Team, platformMeta.Domain),
internal.INGRESS_AUTH_RESPONSE_HEADERS_KEY: internal.INGRESS_AUTH_RESPONSE_HEADERS_VAL,
internal.INGRESS_CONFIGURATION_SNIPPET_KEY: internal.INGRESS_IDE_CONFIGURATION_SNIPPET_VAL,
internal.INGRESS_CERT_MANAGER_KEY: internal.INGRESS_CERT_MANAGER_VAL,
@@ -116,14 +117,14 @@ func GetROS2BridgeIngress(ros2Bridge *robotv1alpha2.ROS2Bridge, ingressNamespace
TLS: []networkingv1.IngressTLS{
{
Hosts: []string{
- tenancy.Team + "." + tenancy.Domain,
+ tenancy.Team + "." + platformMeta.Domain,
},
SecretName: ros2Bridge.Spec.TLSSecretName,
},
},
Rules: []networkingv1.IngressRule{
{
- Host: tenancy.Team + "." + tenancy.Domain,
+ Host: tenancy.Team + "." + platformMeta.Domain,
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
diff --git a/internal/shared.go b/internal/shared.go
index a12dcd7c..2aac2598 100644
--- a/internal/shared.go
+++ b/internal/shared.go
@@ -2,9 +2,10 @@ package internal
import corev1 "k8s.io/api/core/v1"
-// Platform related labels
+// Platform meta labels
const (
PLATFORM_VERSION_LABEL_KEY = "robolaunch.io/platform"
+ DOMAIN_LABEL_KEY = "robolaunch.io/domain"
)
// Tenancy labels
@@ -15,7 +16,6 @@ const (
CLOUD_INSTANCE_LABEL_KEY = "robolaunch.io/cloud-instance"
CLOUD_INSTANCE_ALIAS_LABEL_KEY = "robolaunch.io/cloud-instance-alias"
PHYSICAL_INSTANCE_LABEL_KEY = "robolaunch.io/physical-instance"
- DOMAIN_LABEL_KEY = "robolaunch.io/domain"
)
// Selector labels
@@ -30,12 +30,12 @@ const (
TZ_CITY_LABEL_KEY = "robolaunch.io/tz-city"
)
-// Ready robot label
+// Image labels
const (
- ROBOT_IMAGE_REGISTRY_LABEL_KEY = "robolaunch.io/robot-image-registry"
- ROBOT_IMAGE_USER_LABEL_KEY = "robolaunch.io/robot-image-user"
- ROBOT_IMAGE_REPOSITORY_LABEL_KEY = "robolaunch.io/robot-image-repository"
- ROBOT_IMAGE_TAG_LABEL_KEY = "robolaunch.io/robot-image-tag"
+ IMAGE_REGISTRY_LABEL_KEY = "robolaunch.io/robot-image-registry"
+ IMAGE_USER_LABEL_KEY = "robolaunch.io/robot-image-user"
+ IMAGE_REPOSITORY_LABEL_KEY = "robolaunch.io/robot-image-repository"
+ IMAGE_TAG_LABEL_KEY = "robolaunch.io/robot-image-tag"
)
// Target resource labels
@@ -54,6 +54,20 @@ const (
ROBOT_DEV_SUITE_OWNED = "robolaunch.io/dev-suite-owned"
)
+// Platform server
+const (
+ DEFAULT_PLATFORM_URL = "https://raw.githubusercontent.com/robolaunch/platform/main/platforms/industry-cloud-platform/platform.yaml"
+ DEFAULT_ENVIRONMENTS_URL = "https://raw.githubusercontent.com/robolaunch/platform/main/platforms/industry-cloud-platform/environments.yaml"
+ DEFAULT_TOOLS_ENVIRONMENTS_URL = "https://raw.githubusercontent.com/robolaunch/platform/main/platforms/internal/environments.yaml"
+)
+
+// CodeEditor
+
+const (
+ CODE_EDITOR_APP_NAME = "code-editor"
+ CODE_EDITOR_PORT_NAME = "code-editor"
+)
+
// Robot owned resources' postfixes (v1alpha1)
const (
PVC_VAR_POSTFIX = "-pvc-var"
@@ -75,6 +89,8 @@ const (
PVC_POSTFIX = "-pvc"
STATEFULSET_POSTFIX = ""
DEPLOYMENT_POSTFIX = ""
+ SERVICE_POSTFIX = ""
+ INGRESS_POSTFIX = ""
)
// WorkspaceManager owned resources' postfixes
diff --git a/main.go b/main.go
index 66217c3f..fc507ee4 100644
--- a/main.go
+++ b/main.go
@@ -365,8 +365,9 @@ func startToolkitCRDsAndWebhooks(mgr manager.Manager) {
// ROS2Bridge controller & webhook
if err := (&codeEditor.CodeEditorReconciler{
- Client: mgr.GetClient(),
- Scheme: mgr.GetScheme(),
+ Client: mgr.GetClient(),
+ Scheme: mgr.GetScheme(),
+ Recorder: mgr.GetEventRecorderFor("code-editor"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CodeEditor")
os.Exit(1)
diff --git a/pkg/api/roboscale.io/v1alpha1/robot_types.go b/pkg/api/roboscale.io/v1alpha1/robot_types.go
index 403cce47..88a5a0e6 100644
--- a/pkg/api/roboscale.io/v1alpha1/robot_types.go
+++ b/pkg/api/roboscale.io/v1alpha1/robot_types.go
@@ -415,7 +415,8 @@ const (
// DiscoveryServerSpec defines the desired state of DiscoveryServer.
type DiscoveryServerSpec struct {
- Protocol corev1.Protocol `json:"protocol,omitempty"`
+ // +kubebuilder:validation:Enum=TCP;UDP
+ Protocol string `json:"protocol,omitempty"`
// ROS domain ID for robot. See https://docs.ros.org/en/foxy/Concepts/About-Domain-ID.html.
// +kubebuilder:validation:Minimum=0
// +kubebuilder:validation:Maximum=101
diff --git a/pkg/api/roboscale.io/v1alpha1/robot_webhook.go b/pkg/api/roboscale.io/v1alpha1/robot_webhook.go
index 4b86a0d7..5c7f32a3 100644
--- a/pkg/api/roboscale.io/v1alpha1/robot_webhook.go
+++ b/pkg/api/roboscale.io/v1alpha1/robot_webhook.go
@@ -39,6 +39,7 @@ func (r *Robot) Default() {
_ = r.setRepositoryInfo()
r.setWorkspacesPath()
r.setDiscoveryServerDomainID()
+ r.setDiscoveryServerProtocol()
}
//+kubebuilder:webhook:path=/validate-robot-roboscale-io-v1alpha1-robot,mutating=false,failurePolicy=fail,sideEffects=None,groups=robot.roboscale.io,resources=robots,verbs=create;update,versions=v1alpha1,name=vrobot.kb.io,admissionReviewVersions=v1
@@ -273,8 +274,8 @@ func (r *Robot) checkAdditionalConfigs() error {
}
func (r *Robot) setDefaultLabels() {
- if _, ok := r.Labels[internal.ROBOT_IMAGE_REGISTRY_LABEL_KEY]; !ok {
- r.Labels[internal.ROBOT_IMAGE_REGISTRY_LABEL_KEY] = "docker.io"
+ if _, ok := r.Labels[internal.IMAGE_REGISTRY_LABEL_KEY]; !ok {
+ r.Labels[internal.IMAGE_REGISTRY_LABEL_KEY] = "docker.io"
}
}
@@ -336,6 +337,12 @@ func (r *Robot) setDiscoveryServerDomainID() {
}
}
+func (r *Robot) setDiscoveryServerProtocol() {
+ if reflect.DeepEqual(r.Spec.Type, TypeRobot) {
+ r.Spec.RobotConfig.DiscoveryServerTemplate.Protocol = "UDP"
+ }
+}
+
// ********************************
// DiscoveryServer webhooks
// ********************************
diff --git a/pkg/api/roboscale.io/v1alpha1/shared_types.go b/pkg/api/roboscale.io/v1alpha1/shared_types.go
index 3716a6ea..1831d0fd 100644
--- a/pkg/api/roboscale.io/v1alpha1/shared_types.go
+++ b/pkg/api/roboscale.io/v1alpha1/shared_types.go
@@ -142,7 +142,8 @@ func GetRelayServerServicePath(rs RelayServer, postfix string) string {
func GetServiceDNS(obj metav1.Object, prefix, postfix string) string {
tenancy := label.GetTenancy(obj)
- connectionStr := tenancy.Team + "." + tenancy.Domain + GetServicePath(obj, postfix)
+ platformMeta := label.GetPlatformMeta(obj)
+ connectionStr := tenancy.Team + "." + platformMeta.Domain + GetServicePath(obj, postfix)
if prefix != "" {
connectionStr = prefix + connectionStr
@@ -167,7 +168,8 @@ func GetServicePath(obj metav1.Object, postfix string) string {
func GetServiceDNSWithNodePort(obj metav1.Object, prefix, port string) string {
tenancy := label.GetTenancy(obj)
- connectionStr := tenancy.Team + "." + tenancy.Domain + ":" + port
+ platformMeta := label.GetPlatformMeta(obj)
+ connectionStr := tenancy.Team + "." + platformMeta.Domain + ":" + port
if prefix != "" {
connectionStr = prefix + connectionStr
diff --git a/pkg/api/roboscale.io/v1alpha2/phases.go b/pkg/api/roboscale.io/v1alpha2/phases.go
index b23557a7..90607750 100644
--- a/pkg/api/roboscale.io/v1alpha2/phases.go
+++ b/pkg/api/roboscale.io/v1alpha2/phases.go
@@ -24,7 +24,6 @@ const (
type CodeEditorPhase string
const (
- CodeEditorPhaseCreatingPVCs CodeEditorPhase = "CreatingPVCs"
- CodeEditorPhaseCreatingDeployment CodeEditorPhase = "CreatingDeployment"
- CodeEditorPhaseReady CodeEditorPhase = "Ready"
+ CodeEditorPhaseConfiguringResources CodeEditorPhase = "ConfiguringResources"
+ CodeEditorPhaseReady CodeEditorPhase = "Ready"
)
diff --git a/pkg/api/roboscale.io/v1alpha2/shared_types.go b/pkg/api/roboscale.io/v1alpha2/shared_types.go
index f8984dae..ef75aeb3 100644
--- a/pkg/api/roboscale.io/v1alpha2/shared_types.go
+++ b/pkg/api/roboscale.io/v1alpha2/shared_types.go
@@ -1,8 +1,10 @@
package v1alpha2
import (
+ "github.com/robolaunch/robot-operator/internal/label"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Generic status for any owned resource.
@@ -48,3 +50,48 @@ type OwnedDeploymentStatus struct {
// Container statuses.
ContainerStatuses []corev1.ContainerStatus `json:"containerStatuses,omitempty"`
}
+
+type OwnedServiceStatus struct {
+ // Generic status for any owned resource.
+ Resource OwnedResourceStatus `json:"resource,omitempty"`
+ // Connection URL.
+ URLs map[string]string `json:"urls,omitempty"`
+}
+
+func GetServiceDNS(obj metav1.Object, prefix, postfix string) string {
+ tenancy := label.GetTenancy(obj)
+ platformMeta := label.GetPlatformMeta(obj)
+ connectionStr := tenancy.Team + "." + platformMeta.Domain + GetServicePath(obj, postfix)
+
+ if prefix != "" {
+ connectionStr = prefix + connectionStr
+ }
+
+ return connectionStr
+}
+
+func GetServiceDNSWithNodePort(obj metav1.Object, prefix, port string) string {
+ tenancy := label.GetTenancy(obj)
+ platformMeta := label.GetPlatformMeta(obj)
+ connectionStr := tenancy.Team + "." + platformMeta.Domain + ":" + port
+
+ if prefix != "" {
+ connectionStr = prefix + connectionStr
+ }
+
+ return connectionStr
+}
+
+func GetServicePath(obj metav1.Object, postfix string) string {
+ tenancy := label.GetTenancy(obj)
+ connectionStr := "/" + tenancy.Region +
+ "/" + tenancy.CloudInstanceAlias +
+ "/" + obj.GetNamespace() +
+ "/" + obj.GetName()
+
+ if postfix != "" {
+ connectionStr = connectionStr + postfix
+ }
+
+ return connectionStr
+}
diff --git a/pkg/api/roboscale.io/v1alpha2/toolkit_helpers.go b/pkg/api/roboscale.io/v1alpha2/toolkit_helpers.go
index 242f91e8..736be395 100644
--- a/pkg/api/roboscale.io/v1alpha2/toolkit_helpers.go
+++ b/pkg/api/roboscale.io/v1alpha2/toolkit_helpers.go
@@ -49,3 +49,17 @@ func (codeEditor *CodeEditor) GetDeploymentMetadata() *types.NamespacedName {
Namespace: codeEditor.Namespace,
}
}
+
+func (codeEditor *CodeEditor) GetServiceMetadata() *types.NamespacedName {
+ return &types.NamespacedName{
+ Name: codeEditor.Name + internal.SERVICE_POSTFIX,
+ Namespace: codeEditor.Namespace,
+ }
+}
+
+func (codeEditor *CodeEditor) GetIngressMetadata() *types.NamespacedName {
+ return &types.NamespacedName{
+ Name: codeEditor.Name + internal.INGRESS_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 aa5e5421..bb8289f9 100644
--- a/pkg/api/roboscale.io/v1alpha2/toolkit_types.go
+++ b/pkg/api/roboscale.io/v1alpha2/toolkit_types.go
@@ -128,8 +128,9 @@ type CodeEditorContainer struct {
// 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"`
+ // App version of the code editor.
+ // +kubebuilder:default="4.22.0"
+ Version string `json:"version"`
// If `true`, code editor will be consumed remotely.
Remote bool `json:"remote,omitempty"`
// Configurational parameters for code editor container.
@@ -137,6 +138,14 @@ type CodeEditorSpec struct {
// Port that code editor will use inside the container.
// +kubebuilder:default=9000
Port int32 `json:"port"`
+ // Service type of CodeEditor. `ClusterIP` and `NodePort` is supported.
+ // +kubebuilder:validation:Enum=ClusterIP;NodePort
+ // +kubebuilder:default="ClusterIP"
+ ServiceType corev1.ServiceType `json:"serviceType,omitempty"`
+ // CodeEditor will create an Ingress resource if `true`.
+ Ingress bool `json:"ingress,omitempty"`
+ // Name of the TLS secret for ingress resource.
+ TLSSecretName string `json:"tlsSecretName,omitempty"`
// Volume templates for ROS 2 workload.
// For each volume template, operator will create a PersistentVolumeClaim
// that can be mounted to the ROS 2 workload.
@@ -155,4 +164,10 @@ type CodeEditorStatus struct {
ExternalVolumeStatuses []ExternalVolumeStatus `json:"externalVolumeStatuses,omitempty"`
// Status of code editor deployment.
DeploymentStatus OwnedDeploymentStatus `json:"deploymentStatus,omitempty"`
+ // Status of code editor service.
+ ServiceStatus OwnedServiceStatus `json:"serviceStatus,omitempty"`
+ // Status of CodeEditor Ingress.
+ IngressStatus OwnedResourceStatus `json:"ingressStatus,omitempty"`
+ // Field to indicate if the workload should be restarted.
+ WorkloadUpdateNeeded bool `json:"workloadUpdateNeeded,omitempty"`
}
diff --git a/pkg/api/roboscale.io/v1alpha2/toolkit_webhook.go b/pkg/api/roboscale.io/v1alpha2/toolkit_webhook.go
index 196f745e..4027b4aa 100644
--- a/pkg/api/roboscale.io/v1alpha2/toolkit_webhook.go
+++ b/pkg/api/roboscale.io/v1alpha2/toolkit_webhook.go
@@ -20,6 +20,9 @@ import (
"errors"
"reflect"
+ "github.com/robolaunch/robot-operator/internal"
+ "github.com/robolaunch/robot-operator/internal/label"
+ "github.com/robolaunch/robot-operator/internal/platform"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
@@ -105,12 +108,24 @@ 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)
+
+ err := r.validateVersion()
+ if err != nil {
+ return err
+ }
+
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)
+
+ err := r.validateVersion()
+ if err != nil {
+ return err
+ }
+
return nil
}
@@ -119,3 +134,14 @@ func (r *CodeEditor) ValidateDelete() error {
codeeditorlog.Info("validate delete", "name", r.Name)
return nil
}
+
+func (r *CodeEditor) validateVersion() error {
+ platformMeta := label.GetPlatformMeta(r)
+
+ _, err := platform.GetToolsImage(r, platformMeta.Version, internal.CODE_EDITOR_APP_NAME, r.Spec.Version)
+ if err != nil {
+ return err
+ }
+
+ 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 e663b3cc..98898c47 100644
--- a/pkg/api/roboscale.io/v1alpha2/zz_generated.deepcopy.go
+++ b/pkg/api/roboscale.io/v1alpha2/zz_generated.deepcopy.go
@@ -154,6 +154,8 @@ func (in *CodeEditorStatus) DeepCopyInto(out *CodeEditorStatus) {
copy(*out, *in)
}
in.DeploymentStatus.DeepCopyInto(&out.DeploymentStatus)
+ in.ServiceStatus.DeepCopyInto(&out.ServiceStatus)
+ out.IngressStatus = in.IngressStatus
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CodeEditorStatus.
@@ -259,6 +261,29 @@ func (in *OwnedResourceStatus) DeepCopy() *OwnedResourceStatus {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OwnedServiceStatus) DeepCopyInto(out *OwnedServiceStatus) {
+ *out = *in
+ out.Resource = in.Resource
+ if in.URLs != nil {
+ in, out := &in.URLs, &out.URLs
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnedServiceStatus.
+func (in *OwnedServiceStatus) DeepCopy() *OwnedServiceStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(OwnedServiceStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OwnedStatefulSetStatus) DeepCopyInto(out *OwnedStatefulSetStatus) {
*out = *in
diff --git a/pkg/controllers/v1alpha2/production/ros2_workload/register.go b/pkg/controllers/v1alpha2/production/ros2_workload/register.go
index 70a75fc6..fa4e3c0b 100644
--- a/pkg/controllers/v1alpha2/production/ros2_workload/register.go
+++ b/pkg/controllers/v1alpha2/production/ros2_workload/register.go
@@ -13,6 +13,22 @@ func (r *ROS2WorkloadReconciler) registerPVCs(ctx context.Context, instance *rob
pvcStatuses := []robotv1alpha2.OwnedPVCStatus{}
if len(instance.Spec.VolumeClaimTemplates) != len(instance.Status.PVCStatuses) {
+
+ if len(instance.Status.PVCStatuses) > len(instance.Spec.VolumeClaimTemplates) {
+ for key := len(instance.Spec.VolumeClaimTemplates); key < len(instance.Status.PVCStatuses); key++ {
+ pvc := corev1.PersistentVolumeClaim{}
+ err := r.Get(ctx, *instance.GetPersistentVolumeClaimMetadata(key), &pvc)
+ if err != nil {
+ return err
+ }
+
+ err = r.Delete(ctx, &pvc)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
for key := range instance.Spec.VolumeClaimTemplates {
pvcStatus := robotv1alpha2.OwnedPVCStatus{
Resource: robotv1alpha2.OwnedResourceStatus{
diff --git a/pkg/controllers/v1alpha2/production/ros2_workload/update.go b/pkg/controllers/v1alpha2/production/ros2_workload/update.go
index d866e8ed..ff89d50d 100644
--- a/pkg/controllers/v1alpha2/production/ros2_workload/update.go
+++ b/pkg/controllers/v1alpha2/production/ros2_workload/update.go
@@ -4,7 +4,6 @@ import (
"context"
v1alpha2_resources "github.com/robolaunch/robot-operator/internal/resources/v1alpha2"
- "k8s.io/apimachinery/pkg/api/errors"
ctrl "sigs.k8s.io/controller-runtime"
robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2"
@@ -20,9 +19,7 @@ func (r *ROS2WorkloadReconciler) updateDiscoveryServer(ctx context.Context, inst
}
err = r.Update(ctx, discoveryServer)
- if err != nil && errors.IsAlreadyExists(err) {
- return nil
- } else if err != nil {
+ if err != nil {
return err
}
@@ -40,9 +37,7 @@ func (r *ROS2WorkloadReconciler) updateROS2Bridge(ctx context.Context, instance
}
err = r.Update(ctx, ros2Bridge)
- if err != nil && errors.IsAlreadyExists(err) {
- return nil
- } else if err != nil {
+ if err != nil {
return err
}
@@ -65,9 +60,7 @@ func (r *ROS2WorkloadReconciler) updateStatefulSet(ctx context.Context, instance
}
err = r.Update(ctx, statefulSet)
- if err != nil && errors.IsAlreadyExists(err) {
- return nil
- } else if err != nil {
+ if err != nil {
return err
}
diff --git a/pkg/controllers/v1alpha2/toolkit/code_editor/check.go b/pkg/controllers/v1alpha2/toolkit/code_editor/check.go
index 61c7049c..2c335168 100644
--- a/pkg/controllers/v1alpha2/toolkit/code_editor/check.go
+++ b/pkg/controllers/v1alpha2/toolkit/code_editor/check.go
@@ -2,13 +2,23 @@ package code_editor
import (
"context"
+ "errors"
+ "reflect"
+ "strconv"
+ "github.com/robolaunch/robot-operator/internal"
+ "github.com/robolaunch/robot-operator/internal/label"
+ "github.com/robolaunch/robot-operator/internal/platform"
"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"
+ networkingv1 "k8s.io/api/networking/v1"
+ k8sErr "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/types"
+ "sigs.k8s.io/controller-runtime/pkg/client"
)
func (r *CodeEditorReconciler) reconcileCheckPVCs(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
@@ -17,7 +27,7 @@ func (r *CodeEditorReconciler) reconcileCheckPVCs(ctx context.Context, instance
pvcQuery := &corev1.PersistentVolumeClaim{}
err := r.Get(ctx, *instance.GetPersistentVolumeClaimMetadata(key), pvcQuery)
- if err != nil && errors.IsNotFound(err) {
+ if err != nil && k8sErr.IsNotFound(err) {
pvcStatus.Resource.Created = false
} else if err != nil {
return err
@@ -43,7 +53,7 @@ func (r *CodeEditorReconciler) reconcileCheckExternalVolumes(ctx context.Context
Namespace: instance.Namespace,
Name: evStatus.Name,
}, pvcQuery)
- if err != nil && errors.IsNotFound(err) {
+ if err != nil && k8sErr.IsNotFound(err) {
evStatus.Exists = false
} else if err != nil {
return err
@@ -62,13 +72,75 @@ func (r *CodeEditorReconciler) reconcileCheckDeployment(ctx context.Context, ins
deploymentQuery := &appsv1.Deployment{}
err := r.Get(ctx, *instance.GetDeploymentMetadata(), deploymentQuery)
- if err != nil && errors.IsNotFound(err) {
+ if err != nil && k8sErr.IsNotFound(err) {
instance.Status.DeploymentStatus = robotv1alpha2.OwnedDeploymentStatus{}
} else if err != nil {
return err
} else {
- // make deployment dynamic
+ platformMeta := label.GetPlatformMeta(instance)
+
+ desiredImage, err := platform.GetToolsImage(instance, platformMeta.Version, internal.CODE_EDITOR_APP_NAME, instance.Spec.Version)
+ if err != nil {
+ return err
+ }
+
+ actualImage := deploymentQuery.Spec.Template.Spec.Containers[0].Image
+
+ imageSynced := reflect.DeepEqual(desiredImage, actualImage)
+
+ remoteConfigSynced := (instance.Spec.Remote && reflect.DeepEqual(deploymentQuery.Spec.Template.Spec.Hostname, instance.Name) && reflect.DeepEqual(deploymentQuery.Spec.Template.Spec.Subdomain, instance.Name)) ||
+ (!instance.Spec.Remote && reflect.DeepEqual(deploymentQuery.Spec.Template.Spec.Hostname, "") && reflect.DeepEqual(deploymentQuery.Spec.Template.Spec.Subdomain, ""))
+
+ volumeMountsSynced := reflect.DeepEqual(instance.Spec.Container.VolumeMounts, deploymentQuery.Spec.Template.Spec.Containers[0].VolumeMounts)
+
+ desiredPort := instance.Spec.Port
+ var actualPort int32
+ if len(deploymentQuery.Spec.Template.Spec.Containers) > 0 {
+ cont := deploymentQuery.Spec.Template.Spec.Containers[0]
+ for _, cPort := range cont.Ports {
+ if cPort.Name == internal.CODE_EDITOR_PORT_NAME {
+ actualPort = cPort.ContainerPort
+ }
+ }
+ }
+
+ portSynced := reflect.DeepEqual(desiredPort, actualPort)
+
+ if !imageSynced ||
+ !remoteConfigSynced ||
+ !volumeMountsSynced ||
+ !portSynced ||
+ instance.Status.WorkloadUpdateNeeded {
+ err := r.updateDeployment(ctx, instance)
+ if err != nil {
+ return err
+ }
+ instance.Status.WorkloadUpdateNeeded = false
+ }
+
+ // update container statuses
+ newReq, err := labels.NewRequirement(internal.CODE_EDITOR_CONTAINER_SELECTOR_LABEL_KEY, selection.In, []string{instance.Name})
+ if err != nil {
+ return err
+ }
+ podSelector := labels.NewSelector().Add([]labels.Requirement{*newReq}...)
+
+ podList := corev1.PodList{}
+ err = r.List(ctx, &podList, &client.ListOptions{
+ LabelSelector: podSelector,
+ })
+ if err != nil && k8sErr.IsNotFound(err) {
+ instance.Status.DeploymentStatus.ContainerStatuses = []corev1.ContainerStatus{}
+ } else if err != nil {
+ return err
+ } else {
+ containerStatuses := []corev1.ContainerStatus{}
+ for _, pod := range podList.Items {
+ containerStatuses = append(containerStatuses, pod.Status.ContainerStatuses...)
+ }
+ instance.Status.DeploymentStatus.ContainerStatuses = containerStatuses
+ }
instance.Status.DeploymentStatus.Resource.Created = true
reference.SetReference(&instance.Status.DeploymentStatus.Resource.Reference, deploymentQuery.TypeMeta, deploymentQuery.ObjectMeta)
@@ -76,3 +148,163 @@ func (r *CodeEditorReconciler) reconcileCheckDeployment(ctx context.Context, ins
return nil
}
+
+func (r *CodeEditorReconciler) reconcileCheckService(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
+
+ serviceQuery := &corev1.Service{}
+ err := r.Get(ctx, *instance.GetServiceMetadata(), serviceQuery)
+ if err != nil && k8sErr.IsNotFound(err) {
+ instance.Status.ServiceStatus = robotv1alpha2.OwnedServiceStatus{}
+ } else if err != nil {
+ return err
+ } else {
+
+ remoteConfigSynced := (instance.Spec.Remote && reflect.DeepEqual(serviceQuery.Spec.ClusterIP, corev1.ClusterIPNone)) ||
+ (!instance.Spec.Remote && reflect.DeepEqual(serviceQuery.Spec.Type, instance.Spec.ServiceType) && !reflect.DeepEqual(serviceQuery.Spec.ClusterIP, corev1.ClusterIPNone))
+
+ desiredPort := instance.Spec.Port
+ var actualPort int32
+ for _, sPort := range serviceQuery.Spec.Ports {
+ if sPort.Name == internal.CODE_EDITOR_PORT_NAME {
+ actualPort = sPort.Port
+ }
+ }
+
+ portSynced := reflect.DeepEqual(desiredPort, actualPort)
+
+ serviceTypeSynced := instance.Spec.Remote || (!instance.Spec.Remote && reflect.DeepEqual(instance.Spec.ServiceType, serviceQuery.Spec.Type))
+
+ if !remoteConfigSynced {
+ err := r.Delete(ctx, serviceQuery)
+ if err != nil {
+ return err
+ }
+ r.Recorder.Event(instance, "Normal", "Deleted", "Service '"+instance.GetServiceMetadata().Name+"' is deleted to sync resources.")
+ instance.Status.ServiceStatus = robotv1alpha2.OwnedServiceStatus{}
+ return nil
+ }
+
+ if !portSynced ||
+ !serviceTypeSynced {
+ err := r.updateService(ctx, instance)
+ if err != nil {
+ return err
+ }
+ }
+
+ // set url(s)
+
+ urls := map[string]string{}
+
+ if instance.Spec.Remote {
+
+ // this part will be populated after implementing relay mechanism
+
+ } else {
+
+ if instance.Spec.Ingress {
+
+ urls = map[string]string{
+ internal.CODE_EDITOR_PORT_NAME: robotv1alpha2.GetServiceDNS(instance, "https://", "/"+internal.CODE_EDITOR_APP_NAME),
+ internal.FILE_BROWSER_PORT_NAME: robotv1alpha2.GetServiceDNS(instance, "https://", "/"+internal.FILE_BROWSER_PORT_NAME),
+ }
+
+ } else {
+
+ if instance.Spec.ServiceType == corev1.ServiceTypeNodePort {
+
+ if len(serviceQuery.Spec.Ports) == 2 {
+
+ var ceNodePort int32
+ var fbNodePort int32
+ for _, sPort := range serviceQuery.Spec.Ports {
+ if sPort.Name == internal.CODE_EDITOR_PORT_NAME {
+ ceNodePort = sPort.NodePort
+ }
+ if sPort.Name == internal.FILE_BROWSER_PORT_NAME {
+ fbNodePort = sPort.NodePort
+ }
+ }
+
+ urls = map[string]string{
+ internal.CODE_EDITOR_PORT_NAME: robotv1alpha2.GetServiceDNSWithNodePort(instance, "http://", strconv.Itoa(int(ceNodePort))),
+ internal.FILE_BROWSER_PORT_NAME: robotv1alpha2.GetServiceDNSWithNodePort(instance, "http://", strconv.Itoa(int(fbNodePort))),
+ }
+ } else {
+ return errors.New("wrong number of ports in service")
+ }
+
+ }
+
+ }
+
+ }
+
+ instance.Status.ServiceStatus.URLs = urls
+ instance.Status.ServiceStatus.Resource.Created = true
+ reference.SetReference(&instance.Status.ServiceStatus.Resource.Reference, serviceQuery.TypeMeta, serviceQuery.ObjectMeta)
+ }
+
+ return nil
+}
+
+func (r *CodeEditorReconciler) reconcileCheckIngress(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
+
+ ingressQuery := &networkingv1.Ingress{}
+ err := r.Get(ctx, *instance.GetIngressMetadata(), ingressQuery)
+ if err != nil && k8sErr.IsNotFound(err) {
+ instance.Status.IngressStatus = robotv1alpha2.OwnedResourceStatus{}
+ } else if err != nil {
+ return err
+ } else {
+
+ desiredPort := instance.Spec.Port
+ var actualPort int32
+ if len(ingressQuery.Spec.Rules) > 0 {
+ rule := ingressQuery.Spec.Rules[0]
+ if len(rule.HTTP.Paths) > 0 {
+ codeEditorPath := rule.HTTP.Paths[0]
+ actualPort = codeEditorPath.Backend.Service.Port.Number
+ } else {
+ return errors.New("wrong number of ingress paths")
+ }
+ } else {
+ return errors.New("wrong number of ingress rules")
+ }
+
+ portSynced := reflect.DeepEqual(desiredPort, actualPort)
+
+ actualSecretName := ""
+ if len(ingressQuery.Spec.TLS) > 0 {
+ tls := ingressQuery.Spec.TLS[0]
+ actualSecretName = tls.SecretName
+ } else {
+ return errors.New("ingress host is not found")
+ }
+
+ secretNameSynced := reflect.DeepEqual(instance.Spec.TLSSecretName, actualSecretName)
+
+ if !portSynced ||
+ !secretNameSynced {
+ err := r.updateIngress(ctx, instance)
+ if err != nil {
+ return err
+ }
+ }
+
+ if !instance.Spec.Ingress {
+ err := r.Delete(ctx, ingressQuery)
+ if err != nil {
+ return err
+ }
+ r.Recorder.Event(instance, "Normal", "Deleted", "Ingress '"+instance.GetIngressMetadata().Name+"' is deleted to sync resources.")
+
+ instance.Status.IngressStatus = robotv1alpha2.OwnedResourceStatus{}
+ }
+
+ instance.Status.IngressStatus.Created = true
+ reference.SetReference(&instance.Status.IngressStatus.Reference, ingressQuery.TypeMeta, ingressQuery.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
index 3eb22eb6..febfd29f 100644
--- a/pkg/controllers/v1alpha2/toolkit/code_editor/codeeditor_controller.go
+++ b/pkg/controllers/v1alpha2/toolkit/code_editor/codeeditor_controller.go
@@ -19,11 +19,14 @@ package code_editor
import (
"context"
+ "github.com/robolaunch/robot-operator/internal"
robotErr "github.com/robolaunch/robot-operator/internal/error"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
+ networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -35,7 +38,8 @@ import (
// CodeEditorReconciler reconciles a CodeEditor object
type CodeEditorReconciler struct {
client.Client
- Scheme *runtime.Scheme
+ Scheme *runtime.Scheme
+ Recorder record.EventRecorder
}
//+kubebuilder:rbac:groups=robot.roboscale.io,resources=codeeditors,verbs=get;list;watch;create;update;patch;delete
@@ -44,6 +48,9 @@ type CodeEditorReconciler struct {
//+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
+//+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;update;patch;delete
var logger logr.Logger
@@ -60,11 +67,14 @@ func (r *CodeEditorReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, err
}
- r.reconcileRegisterResources(instance)
+ err = r.reconcileRegisterResources(ctx, instance)
+ if err != nil {
+ return result, err
+ }
err = r.reconcileUpdateInstanceStatus(ctx, instance)
if err != nil {
- return ctrl.Result{}, err
+ return result, err
}
err = r.reconcileCheckStatus(ctx, instance, &result)
@@ -74,26 +84,33 @@ func (r *CodeEditorReconciler) Reconcile(ctx context.Context, req ctrl.Request)
err = r.reconcileUpdateInstanceStatus(ctx, instance)
if err != nil {
- return ctrl.Result{}, err
+ return result, err
}
err = r.reconcileCheckResources(ctx, instance)
if err != nil {
- return ctrl.Result{}, err
+ return result, err
}
err = r.reconcileUpdateInstanceStatus(ctx, instance)
if err != nil {
- return ctrl.Result{}, err
+ return result, err
}
return result, nil
}
-func (r *CodeEditorReconciler) reconcileRegisterResources(instance *robotv1alpha2.CodeEditor) error {
+func (r *CodeEditorReconciler) reconcileRegisterResources(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
+
+ err := r.registerPVCs(ctx, instance)
+ if err != nil {
+ return err
+ }
- r.registerPVCs(instance)
- r.registerExternalVolumes(instance)
+ err = r.registerExternalVolumes(ctx, instance)
+ if err != nil {
+ return err
+ }
return nil
}
@@ -110,6 +127,16 @@ func (r *CodeEditorReconciler) reconcileCheckStatus(ctx context.Context, instanc
return robotErr.CheckCreatingOrWaitingError(result, err)
}
+ err = r.reconcileHandleService(ctx, instance)
+ if err != nil {
+ return robotErr.CheckCreatingOrWaitingError(result, err)
+ }
+
+ err = r.reconcileHandleIngress(ctx, instance)
+ if err != nil {
+ return robotErr.CheckCreatingOrWaitingError(result, err)
+ }
+
return nil
}
@@ -130,6 +157,44 @@ func (r *CodeEditorReconciler) reconcileCheckResources(ctx context.Context, inst
return err
}
+ err = r.reconcileCheckService(ctx, instance)
+ if err != nil {
+ return err
+ }
+
+ err = r.reconcileCheckIngress(ctx, instance)
+ if err != nil {
+ return err
+ }
+
+ err = r.reconcileCalculatePhase(ctx, instance)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (r *CodeEditorReconciler) reconcileCalculatePhase(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
+
+ containersReady := true
+ if len(instance.Status.DeploymentStatus.ContainerStatuses) > 0 {
+ for _, cStatus := range instance.Status.DeploymentStatus.ContainerStatuses {
+ containersReady = containersReady && cStatus.Ready
+ }
+ } else {
+ containersReady = false
+ }
+
+ if containersReady && instance.Status.ServiceStatus.Resource.Created && (instance.Spec.Ingress == instance.Status.IngressStatus.Created) {
+ if codeEditorURL, ok := instance.Status.ServiceStatus.URLs[internal.CODE_EDITOR_PORT_NAME]; ok && instance.Status.Phase != robotv1alpha2.CodeEditorPhaseReady {
+ r.Recorder.Event(instance, "Normal", "Ready", "CodeEditor is accessible over the URL '"+codeEditorURL+"'.")
+ }
+ instance.Status.Phase = robotv1alpha2.CodeEditorPhaseReady
+ } else {
+ instance.Status.Phase = robotv1alpha2.CodeEditorPhaseConfiguringResources
+ }
+
return nil
}
@@ -139,5 +204,7 @@ func (r *CodeEditorReconciler) SetupWithManager(mgr ctrl.Manager) error {
For(&robotv1alpha2.CodeEditor{}).
Owns(&corev1.PersistentVolumeClaim{}).
Owns(&appsv1.Deployment{}).
+ Owns(&corev1.Service{}).
+ Owns(&networkingv1.Ingress{}).
Complete(r)
}
diff --git a/pkg/controllers/v1alpha2/toolkit/code_editor/create.go b/pkg/controllers/v1alpha2/toolkit/code_editor/create.go
index 2992302e..169210ec 100644
--- a/pkg/controllers/v1alpha2/toolkit/code_editor/create.go
+++ b/pkg/controllers/v1alpha2/toolkit/code_editor/create.go
@@ -25,6 +25,8 @@ func (r *CodeEditorReconciler) createPersistentVolumeClaim(ctx context.Context,
return err
}
+ r.Recorder.Event(instance, "Normal", "Created", "PersistentVolumeClaim '"+instance.GetPersistentVolumeClaimMetadata(key).Name+"' is created.")
+
logger.Info("STATUS: PVC " + instance.GetPersistentVolumeClaimMetadata(key).Name + " is created.")
return nil
}
@@ -50,6 +52,52 @@ func (r *CodeEditorReconciler) createDeployment(ctx context.Context, instance *r
return err
}
+ r.Recorder.Event(instance, "Normal", "Created", "Deployment '"+instance.GetDeploymentMetadata().Name+"' is created.")
+
logger.Info("STATUS: Deployment " + instance.GetDeploymentMetadata().Name + " is created.")
return nil
}
+
+func (r *CodeEditorReconciler) createService(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
+
+ service := v1alpha2_resources.GetCodeEditorService(instance, instance.GetServiceMetadata())
+
+ err := ctrl.SetControllerReference(instance, service, r.Scheme)
+ if err != nil {
+ return err
+ }
+
+ err = r.Create(ctx, service)
+ if err != nil && errors.IsAlreadyExists(err) {
+ return nil
+ } else if err != nil {
+ return err
+ }
+
+ r.Recorder.Event(instance, "Normal", "Created", "Service '"+instance.GetDeploymentMetadata().Name+"' is created.")
+
+ logger.Info("STATUS: Service " + instance.GetServiceMetadata().Name + " is created.")
+ return nil
+}
+
+func (r *CodeEditorReconciler) createIngress(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
+
+ ingress := v1alpha2_resources.GetCodeEditorIngress(instance, instance.GetIngressMetadata())
+
+ err := ctrl.SetControllerReference(instance, ingress, r.Scheme)
+ if err != nil {
+ return err
+ }
+
+ err = r.Create(ctx, ingress)
+ if err != nil && errors.IsAlreadyExists(err) {
+ return nil
+ } else if err != nil {
+ return err
+ }
+
+ r.Recorder.Event(instance, "Normal", "Created", "Ingress '"+instance.GetDeploymentMetadata().Name+"' is created.")
+
+ logger.Info("STATUS: Ingress " + instance.GetIngressMetadata().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
index 78cc7a98..8441ee3c 100644
--- a/pkg/controllers/v1alpha2/toolkit/code_editor/handle.go
+++ b/pkg/controllers/v1alpha2/toolkit/code_editor/handle.go
@@ -13,7 +13,6 @@ func (r *CodeEditorReconciler) reconcileHandlePVCs(ctx context.Context, instance
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
@@ -46,7 +45,6 @@ func (r *CodeEditorReconciler) reconcileHandleDeployment(ctx context.Context, in
if volumesReady && !instance.Status.DeploymentStatus.Resource.Created {
- instance.Status.Phase = robotv1alpha2.CodeEditorPhaseCreatingDeployment
err := r.createDeployment(ctx, instance)
if err != nil {
return err
@@ -63,3 +61,45 @@ func (r *CodeEditorReconciler) reconcileHandleDeployment(ctx context.Context, in
return nil
}
+
+func (r *CodeEditorReconciler) reconcileHandleService(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
+
+ if !instance.Status.ServiceStatus.Resource.Created {
+
+ err := r.createService(ctx, instance)
+ if err != nil {
+ return err
+ }
+
+ instance.Status.ServiceStatus.Resource.Created = true
+
+ return &robotErr.CreatingResourceError{
+ ResourceKind: "Service",
+ ResourceName: instance.GetServiceMetadata().Name,
+ ResourceNamespace: instance.GetServiceMetadata().Namespace,
+ }
+ }
+
+ return nil
+}
+
+func (r *CodeEditorReconciler) reconcileHandleIngress(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
+
+ if instance.Spec.Ingress && !instance.Status.IngressStatus.Created {
+
+ err := r.createIngress(ctx, instance)
+ if err != nil {
+ return err
+ }
+
+ instance.Status.IngressStatus.Created = true
+
+ return &robotErr.CreatingResourceError{
+ ResourceKind: "Ingress",
+ ResourceName: instance.GetIngressMetadata().Name,
+ ResourceNamespace: instance.GetIngressMetadata().Namespace,
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/controllers/v1alpha2/toolkit/code_editor/register.go b/pkg/controllers/v1alpha2/toolkit/code_editor/register.go
index 3c7815d8..8a7c3e33 100644
--- a/pkg/controllers/v1alpha2/toolkit/code_editor/register.go
+++ b/pkg/controllers/v1alpha2/toolkit/code_editor/register.go
@@ -1,15 +1,33 @@
package code_editor
import (
+ "context"
+
robotv1alpha2 "github.com/robolaunch/robot-operator/pkg/api/roboscale.io/v1alpha2"
corev1 "k8s.io/api/core/v1"
)
-func (r *CodeEditorReconciler) registerPVCs(instance *robotv1alpha2.CodeEditor) {
+func (r *CodeEditorReconciler) registerPVCs(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
pvcStatuses := []robotv1alpha2.OwnedPVCStatus{}
if len(instance.Spec.VolumeClaimTemplates) != len(instance.Status.PVCStatuses) {
+
+ if len(instance.Status.PVCStatuses) > len(instance.Spec.VolumeClaimTemplates) {
+ for key := len(instance.Spec.VolumeClaimTemplates); key < len(instance.Status.PVCStatuses); key++ {
+ pvc := corev1.PersistentVolumeClaim{}
+ err := r.Get(ctx, *instance.GetPersistentVolumeClaimMetadata(key), &pvc)
+ if err != nil {
+ return err
+ }
+
+ err = r.Delete(ctx, &pvc)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
for key := range instance.Spec.VolumeClaimTemplates {
pvcStatus := robotv1alpha2.OwnedPVCStatus{
Resource: robotv1alpha2.OwnedResourceStatus{
@@ -22,10 +40,13 @@ func (r *CodeEditorReconciler) registerPVCs(instance *robotv1alpha2.CodeEditor)
pvcStatuses = append(pvcStatuses, pvcStatus)
}
instance.Status.PVCStatuses = pvcStatuses
+ instance.Status.WorkloadUpdateNeeded = true
}
+
+ return nil
}
-func (r *CodeEditorReconciler) registerExternalVolumes(instance *robotv1alpha2.CodeEditor) {
+func (r *CodeEditorReconciler) registerExternalVolumes(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
evStatuses := []robotv1alpha2.ExternalVolumeStatus{}
@@ -37,5 +58,8 @@ func (r *CodeEditorReconciler) registerExternalVolumes(instance *robotv1alpha2.C
evStatuses = append(evStatuses, evStatus)
}
instance.Status.ExternalVolumeStatuses = evStatuses
+ instance.Status.WorkloadUpdateNeeded = true
}
+
+ return nil
}
diff --git a/pkg/controllers/v1alpha2/toolkit/code_editor/update.go b/pkg/controllers/v1alpha2/toolkit/code_editor/update.go
new file mode 100644
index 00000000..c4da573a
--- /dev/null
+++ b/pkg/controllers/v1alpha2/toolkit/code_editor/update.go
@@ -0,0 +1,74 @@
+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"
+ ctrl "sigs.k8s.io/controller-runtime"
+)
+
+func (r *CodeEditorReconciler) updateDeployment(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.Update(ctx, deployment)
+ if err != nil {
+ return err
+ }
+
+ r.Recorder.Event(instance, "Normal", "Updated", "Deployment '"+instance.GetDeploymentMetadata().Name+"' is updated to sync resources.")
+
+ logger.Info("STATUS: Deployment " + deployment.Name + " is updated.")
+ return nil
+}
+
+func (r *CodeEditorReconciler) updateService(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
+
+ service := v1alpha2_resources.GetCodeEditorService(instance, instance.GetServiceMetadata())
+
+ err := ctrl.SetControllerReference(instance, service, r.Scheme)
+ if err != nil {
+ return err
+ }
+
+ err = r.Update(ctx, service)
+ if err != nil {
+ return err
+ }
+
+ r.Recorder.Event(instance, "Normal", "Updated", "Service '"+instance.GetServiceMetadata().Name+"' is updated to sync resources.")
+
+ logger.Info("STATUS: Service " + service.Name + " is updated.")
+ return nil
+}
+
+func (r *CodeEditorReconciler) updateIngress(ctx context.Context, instance *robotv1alpha2.CodeEditor) error {
+
+ ingress := v1alpha2_resources.GetCodeEditorIngress(instance, instance.GetIngressMetadata())
+
+ err := ctrl.SetControllerReference(instance, ingress, r.Scheme)
+ if err != nil {
+ return err
+ }
+
+ err = r.Update(ctx, ingress)
+ if err != nil {
+ return err
+ }
+
+ r.Recorder.Event(instance, "Normal", "Updated", "Ingress '"+instance.GetIngressMetadata().Name+"' is updated to sync resources.")
+
+ logger.Info("STATUS: Ingress " + instance.GetIngressMetadata().Name + " is updated.")
+ return nil
+}