feat: Use a pre-created ServiceAccount for workspace Pods (#1569)

* feat: Use a pre-created ServiceAccount for workspace Pods

Signed-off-by: Anatolii Bazko <abazko@redhat.com>
pull/1577/head
Anatolii Bazko 2022-12-07 15:25:01 +02:00 committed by GitHub
parent 8ef15f9489
commit 783d35046c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 928 additions and 190 deletions

View File

@ -19,7 +19,7 @@
| [github.com/BurntSushi/xgb@27f122750802c950b2c869a5b63dafcf590ced95](https://github.com/BurntSushi/xgb.git) | BSD-3-Clause AND WTFPL | [CQ](https://dev.eclipse.org/ipzilla/show_bug.cgi?id=23291) |
| [sigs.k8s.io/structured-merge-diff/v4@v4.1.0](https://github.com/kubernetes-sigs/structured-merge-diff/releases/tag/v4.1.0.git) | Apache-2.0 | [CQ](https://dev.eclipse.org/ipzilla/show_bug.cgi?id=23293) |
| [sigs.k8s.io/yaml@v1.2.0](https://github.com/kubernetes-sigs/yaml.git) | BSD-3-Clause AND MIT | [CQ](https://dev.eclipse.org/ipzilla/show_bug.cgi?id=23295) |
| [k8s.io/utils@67b214c5f920060488eda08cd2b5c30597f2f1d7](https://github.com/kubernetes/utils.git) | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/git/github/kubernetes/utils/67b214c5f920060488eda08cd2b5c30597f2f1d7) |
| [k8s.io/utils@7f3ee0f3147123c62f9b8a204651aa38fc772d20](https://github.com/kubernetes/utils.git) | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/go/golang/k8s.io/utils/v0.0.0-20210722164352-7f3ee0f31471) |
| [k8s.io/gengo@0689ccc1d7d65d9dd1bedcc3b0b1ed7df91ba266](https://github.com/kubernetes/gengo.git) | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/git/github/kubernetes/gengo/0689ccc1d7d65d9dd1bedcc3b0b1ed7df91ba266) |
| [github.com/PuerkitoBio/urlesc@5bd2802263f21d8788851d5305584c82a5c75d7e](https://github.com/puerkitobio/urlesc.git) | BSD-3-Clause | [clearlydefined](https://clearlydefined.io/definitions/git/github/puerkitobio/urlesc/5bd2802263f21d8788851d5305584c82a5c75d7e) |
| [github.com/census-instrumentation/opencensus-proto@v0.2.1](https://github.com/census-instrumentation/opencensus-proto.git) | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/git/github/census-instrumentation/opencensus-proto/d89fa54de508111353cb0b06403c00569be780d8) |
@ -458,6 +458,6 @@
| [golang.org/x/xerrors@5ec99f83aff198f5fbd629d6c8d8eb38a04218ca](https://cs.opensource.google/go) | BSD-3-Clause | N/A |
| [golang.org/x/oauth2@bf48bf16ab8d622ce64ec6ce98d2c98f916b6303](https://cs.opensource.google/go) | BSD-3-Clause | N/A |
| [golang.org/x/mobile@d3739f865fa66d07c1f506505c18aac71a8ead6e](https://cs.opensource.google/go) | BSD-3-Clause | N/A |
| [github.com/devfile/api/v2@32cae1f8e42c22035138ef6ee93080bc47d751c6](https://github.com/devfile/api.git) | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/git/github/devfile/api/32cae1f8e42c22035138ef6ee93080bc47d751c6) |
| [github.com/devfile/api/v2@fe7c10eaa530b12b19cfb0e22e221e753391304c](https://github.com/devfile/api.git) | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/git/github/devfile/api/fe7c10eaa530b12b19cfb0e22e221e753391304c) |
| [github.com/che-incubator/kubernetes-image-puller-operator@0128446f5af78587c0427a35d693bbb8d24036bc](https://github.com/che-incubator/kubernetes-image-puller-operator.git) | EPL-2.0 | todo |
| [github.com/devfile/devworkspace-operator@0224ca56f4deaaaa342fa0ab4d098303e3ac53e6](https://github.com/devfile/devworkspace-operator.git) | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/git/github/devfile/devworkspace-operator/0224ca56f4deaaaa342fa0ab4d098303e3ac53e6) |
| [github.com/devfile/devworkspace-operator@bd6bffe44a9cc064688214059dfe05865cdfe491](https://github.com/devfile/devworkspace-operator.git) | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/git/github/devfile/devworkspace-operator/bd6bffe44a9cc064688214059dfe05865cdfe491) |

View File

@ -322,7 +322,7 @@ ENVTEST_ASSETS_DIR=$(shell pwd)/testbin
test: download-gateway-resources ## Run tests.
export MOCK_API=true; go test -mod=vendor ./... -coverprofile cover.out
update-go-dependencies: update-go-dependencies ## Update golang dependencies
update-go-dependencies: ## Update golang dependencies
go mod tidy
go mod vendor
@ -762,12 +762,12 @@ install-devworkspace: ## Install Dev Workspace operator, available channels: nex
IMAGE="quay.io/devfile/devworkspace-operator-index:next"
fi
$(MAKE) create-catalogsource IMAGE="$${IMAGE}" NAME="devworkspace-operator"
$(MAKE) create-catalogsource IMAGE="$${IMAGE}" NAME="devworkspace-operator" NAMESPACE="openshift-marketplace"
$(MAKE) create-subscription \
NAME="devworkspace-operator" \
NAMESPACE="openshift-operators" \
PACKAGE_NAME="devworkspace-operator" \
CHANNEL="$(CHANNEL)" \
CHANNEL=$(CHANNEL) \
SOURCE="devworkspace-operator" \
SOURCE_NAMESPACE="openshift-marketplace" \
INSTALL_PLAN_APPROVAL="Auto"

View File

@ -114,6 +114,11 @@ type CheClusterDevEnvironments struct {
// Container build configuration.
// +optional
ContainerBuildConfiguration *ContainerBuildConfiguration `json:"containerBuildConfiguration,omitempty"`
// ServiceAccount to use by the DevWorkspace operator when starting the workspaces.
// +optional
// +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
// +kubebuilder:validation:MaxLength=63
ServiceAccount string `json:"serviceAccount,omitempty"`
}
// Che components configuration.

View File

@ -36,6 +36,8 @@ runTest() {
--k8spodwaittimeout=120000 \
--k8spodreadytimeout=120000 \
--templates "${CURRENT_OPERATOR_VERSION_TEMPLATE_PATH}" \
--k8spodwaittimeout=120000 \
--k8spodreadytimeout=120000 \
--che-operator-cr-patch-yaml "${OPERATOR_REPO}/build/scripts/minikube-tests/minikube-checluster-patch.yaml"
make wait-devworkspace-running NAMESPACE="devworkspace-controller" VERBOSE=1

View File

@ -33,6 +33,8 @@ runTest() {
--k8spodwaittimeout=120000 \
--k8spodreadytimeout=120000 \
--templates ${LAST_OPERATOR_VERSION_TEMPLATE_PATH} \
--k8spodwaittimeout=120000 \
--k8spodreadytimeout=120000 \
--che-operator-cr-patch-yaml "${OPERATOR_REPO}/build/scripts/minikube-tests/minikube-checluster-patch.yaml"
# Free up some cpu resources

View File

@ -29,8 +29,8 @@ catchFinish() {
exit ${RESULT}
}
waitForRemovedEclipseCheSubscription() {
while [[ $(oc get subscription -A -o json | jq -r '.items | .[] | select(.spec.name == "'${ECLIPSE_CHE_PACKAGE_NAME}'")') != "" ]]; do
waitForRemovedSubscription() {
while [[ $(oc get subscription -A -o json | jq -r '.items | .[] | select(.spec.name == "'$1'")') != "" ]]; do
sleep 5s
done
}

View File

@ -31,16 +31,30 @@ deleteEclipseCheStableVersionOperator() {
oc delete csv ${ECLIPSE_CHE_INSTALLED_CSV} -n ${ECLIPSE_CHE_SUBSCRIPTION_NAMESPACE}
oc delete subscription ${ECLIPSE_CHE_SUBSCRIPTION_NAME} -n ${ECLIPSE_CHE_SUBSCRIPTION_NAMESPACE}
waitForRemovedEclipseCheSubscription
waitForRemovedSubscription ${ECLIPSE_CHE_PACKAGE_NAME}
# Hack, since we remove operator pod, webhook won't work.
# We have to disable it for a while.
oc patch crd checlusters.org.eclipse.che --patch '{"spec": {"conversion": null}}' --type=merge
}
deleteDevWorkspaceStableVersionOperator() {
DEV_WORKSPACE_PACKAGE_NAME="devworkspace-operator"
DEV_WORKSPACE_SUBSCRIPTION_RECORD=$(oc get subscription -A -o json | jq -r '.items | .[] | select(.spec.name == "'${DEV_WORKSPACE_PACKAGE_NAME}'")')
DEV_WORKSPACE_SUBSCRIPTION_NAME=$(echo ${DEV_WORKSPACE_SUBSCRIPTION_RECORD} | jq -r '.metadata.name')
DEV_WORKSPACE_SUBSCRIPTION_NAMESPACE=$(echo ${DEV_WORKSPACE_SUBSCRIPTION_RECORD} | jq -r '.metadata.namespace')
DEV_WORKSPACE_INSTALLED_CSV=$(echo ${DEV_WORKSPACE_SUBSCRIPTION_RECORD} | jq -r '.status.installedCSV')
oc delete csv ${DEV_WORKSPACE_INSTALLED_CSV} -n ${DEV_WORKSPACE_SUBSCRIPTION_NAMESPACE}
oc delete subscription ${DEV_WORKSPACE_SUBSCRIPTION_NAME} -n ${DEV_WORKSPACE_SUBSCRIPTION_NAMESPACE}
waitForRemovedSubscription ${DEV_WORKSPACE_PACKAGE_NAME}
}
runTests() {
. ${OPERATOR_REPO}/build/scripts/olm/test-catalog.sh -i quay.io/eclipse/eclipse-che-olm-catalog:stable -c stable --verbose
deleteEclipseCheStableVersionOperator
deleteDevWorkspaceStableVersionOperator
. ${OPERATOR_REPO}/build/scripts/olm/test-catalog-from-sources.sh --verbose
}

View File

@ -145,6 +145,9 @@ createEclipseCheCatalogFromSources() {
run() {
make create-namespace NAMESPACE="${NAMESPACE}" VERBOSE=${VERBOSE}
# Install Dev Workspace operator (next version as well)
make install-devworkspace CHANNEL="next"
exposeOpenShiftRegistry
createEclipseCheCatalogFromSources

View File

@ -56,7 +56,7 @@ usage () {
run() {
make create-namespace NAMESPACE="${NAMESPACE}" VERBOSE=${VERBOSE}
make create-catalogsource NAME="${ECLIPSE_CHE_CATALOG_SOURCE_NAME}" IMAGE="${CATALOG_IMAGE}" VERBOSE=${VERBOSE}
make create-catalogsource NAME="${ECLIPSE_CHE_CATALOG_SOURCE_NAME}" IMAGE="${CATALOG_IMAGE}" VERBOSE=${VERBOSE} NAMESPACE="openshift-marketplace"
discoverEclipseCheBundles "${CHANNEL}"

View File

@ -56,7 +56,7 @@ usage () {
run() {
make create-namespace NAMESPACE="${NAMESPACE}" VERBOSE=${VERBOSE}
make create-catalogsource NAME="${ECLIPSE_CHE_CATALOG_SOURCE_NAME}" IMAGE="${CATALOG_IMAGE}" VERBOSE=${VERBOSE}
make create-catalogsource NAME="${ECLIPSE_CHE_CATALOG_SOURCE_NAME}" IMAGE="${CATALOG_IMAGE}" VERBOSE=${VERBOSE} NAMESPACE="openshift-marketplace"
discoverEclipseCheBundles "${CHANNEL}"

View File

@ -6995,6 +6995,12 @@ spec:
run timeout, set this value to -1.
format: int32
type: integer
serviceAccount:
description: ServiceAccount to use by the DevWorkspace operator
when starting the workspaces.
maxLength: 63
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
storage:
default:
pvcStrategy: per-user

View File

@ -6805,6 +6805,12 @@ spec:
run timeout, set this value to -1.
format: int32
type: integer
serviceAccount:
description: ServiceAccount to use by the DevWorkspace operator
when starting the workspaces.
maxLength: 63
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
storage:
default:
pvcStrategy: per-user

View File

@ -4600,6 +4600,11 @@ spec:
description: Run timeout for workspaces in seconds. This timeout is the maximum duration a workspace runs. To disable workspace run timeout, set this value to -1.
format: int32
type: integer
serviceAccount:
description: ServiceAccount to use by the DevWorkspace operator when starting the workspaces.
maxLength: 63
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
storage:
default:
pvcStrategy: per-user

View File

@ -4595,6 +4595,11 @@ spec:
description: Run timeout for workspaces in seconds. This timeout is the maximum duration a workspace runs. To disable workspace run timeout, set this value to -1.
format: int32
type: integer
serviceAccount:
description: ServiceAccount to use by the DevWorkspace operator when starting the workspaces.
maxLength: 63
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
storage:
default:
pvcStrategy: per-user

View File

@ -4600,6 +4600,11 @@ spec:
description: Run timeout for workspaces in seconds. This timeout is the maximum duration a workspace runs. To disable workspace run timeout, set this value to -1.
format: int32
type: integer
serviceAccount:
description: ServiceAccount to use by the DevWorkspace operator when starting the workspaces.
maxLength: 63
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
storage:
default:
pvcStrategy: per-user

View File

@ -4595,6 +4595,11 @@ spec:
description: Run timeout for workspaces in seconds. This timeout is the maximum duration a workspace runs. To disable workspace run timeout, set this value to -1.
format: int32
type: integer
serviceAccount:
description: ServiceAccount to use by the DevWorkspace operator when starting the workspaces.
maxLength: 63
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
storage:
default:
pvcStrategy: per-user

5
go.mod
View File

@ -5,8 +5,8 @@ go 1.16
require (
github.com/Shopify/logrus-bugsnag v0.0.0-00010101000000-000000000000 // indirect
github.com/che-incubator/kubernetes-image-puller-operator v0.0.0-20210929175054-0128446f5af7
github.com/devfile/api/v2 v2.0.0-20220414122024-32cae1f8e42c
github.com/devfile/devworkspace-operator v0.15.2
github.com/devfile/api/v2 v2.0.0-20220928161623-fe7c10eaa530
github.com/devfile/devworkspace-operator v0.17.0
github.com/go-logr/logr v0.4.0
github.com/golang/mock v1.5.0
github.com/google/go-cmp v0.5.6
@ -388,7 +388,6 @@ replace (
k8s.io/klog/v2 => k8s.io/klog/v2 v2.8.0
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.0.0-20180912235703-14b8d2d93fcb
k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20200923105717-7eba4cbaebdf
k8s.io/utils => k8s.io/utils v0.0.0-20201110183641-67b214c5f920
kubernetes/klog => kubernetes/klog v1.0.0
modernc.org/b => modernc.org/b v1.0.0
modernc.org/db => modernc.org/db v1.0.0

12
go.sum
View File

@ -112,10 +112,10 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As=
github.com/denisenkom/go-mssqldb v0.0.0-20190204142019-df6d76eb9289/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
github.com/devfile/api/v2 v2.0.0-20220414122024-32cae1f8e42c h1:yyidoxal8ngJWDxRuVZMNh4PBwqDIzOkTeOagtmRiy0=
github.com/devfile/api/v2 v2.0.0-20220414122024-32cae1f8e42c/go.mod h1:kLX/nW93gigOHXK3NLeJL2fSS/sgEe+OHu8bo3aoOi4=
github.com/devfile/devworkspace-operator v0.15.2 h1:CcLGHtuBOKdwpeYV8Iy7ZwUSgavcMbjuPA9ejupM7BE=
github.com/devfile/devworkspace-operator v0.15.2/go.mod h1:fM3/GhPWEL8JZOEImCnpyxTYEf9dN6PsjzJq+ffcD1k=
github.com/devfile/api/v2 v2.0.0-20220928161623-fe7c10eaa530 h1:pZvf4AZrf/ZwV2AwQnTInlUpns+Wj9JYtPRtBDiFHzk=
github.com/devfile/api/v2 v2.0.0-20220928161623-fe7c10eaa530/go.mod h1:dN7xFrOVG+iPqn4UKGibXLd5oVsdE8XyK9OEb5JL3aI=
github.com/devfile/devworkspace-operator v0.17.0 h1:Ml8ncEQAVf/d+DTttQ76oe/btnFQpicRG/XncdflOZo=
github.com/devfile/devworkspace-operator v0.17.0/go.mod h1:xLELAolfebwROqGSvOWhdC0eH7S+V7iVFzHxtm3Jf2A=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dhui/dktest v0.3.2/go.mod h1:l1/ib23a/CmxAe7yixtrYPc8Iy90Zy2udyaHINM5p58=
@ -706,8 +706,10 @@ k8s.io/kube-openapi v0.0.0-20200923105717-7eba4cbaebdf h1:7RCqblb9HTvcWeOYwrt1SV
k8s.io/kube-openapi v0.0.0-20200923105717-7eba4cbaebdf/go.mod h1:bfCVj+qXcEaE5SCvzBaqpOySr6tuCcpPKqF6HD8nyCw=
k8s.io/kubectl v0.0.0-20201218185502-10b66c3fd14b/go.mod h1:2bE0JLYTRDVKDiTREFsjLAx4R2GvUtL/mGYFXfFFMzY=
k8s.io/metrics v0.20.2/go.mod h1:yTck5nl5wt/lIeLcU6g0b8/AKJf2girwe0PQiaM4Mwk=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 h1:DnzUXII7sVg1FJ/4JX6YDRJfLNAC7idRatPwe07suiI=
k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg=
modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=
modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=

View File

@ -4595,6 +4595,11 @@ spec:
description: Run timeout for workspaces in seconds. This timeout is the maximum duration a workspace runs. To disable workspace run timeout, set this value to -1.
format: int32
type: integer
serviceAccount:
description: ServiceAccount to use by the DevWorkspace operator when starting the workspaces.
maxLength: 63
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
storage:
default:
pvcStrategy: per-user

View File

@ -20,6 +20,7 @@ import (
"github.com/eclipse-che/che-operator/pkg/deploy"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
@ -54,13 +55,12 @@ func (d *DevWorkspaceConfigReconciler) Reconcile(ctx *chetypes.DeployContext) (r
if dwoc.Config == nil {
dwoc.Config = &controllerv1alpha1.OperatorConfiguration{}
}
err := updateOperatorConfig(ctx.CheCluster.Spec.DevEnvironments.Storage, dwoc.Config)
if err != nil {
if err := updateWorkspaceConfig(&ctx.CheCluster.Spec.DevEnvironments, dwoc.Config); err != nil {
return reconcile.Result{}, false, err
}
done, err := deploy.Sync(ctx, dwoc)
if !done {
if done, err := deploy.Sync(ctx, dwoc); !done {
return reconcile.Result{}, false, err
}
@ -71,52 +71,62 @@ func (d *DevWorkspaceConfigReconciler) Finalize(ctx *chetypes.DeployContext) boo
return true
}
func updateOperatorConfig(storage chev2.WorkspaceStorage, operatorConfig *controllerv1alpha1.OperatorConfiguration) error {
var pvc *chev2.PVC
pvcStrategy := utils.GetValue(storage.PvcStrategy, constants.DefaultPvcStorageStrategy)
switch pvcStrategy {
case constants.CommonPVCStorageStrategy:
fallthrough
case constants.PerUserPVCStorageStrategy:
if storage.PerUserStrategyPvcConfig != nil {
pvc = storage.PerUserStrategyPvcConfig
}
case constants.PerWorkspacePVCStorageStrategy:
if storage.PerWorkspaceStrategyPvcConfig != nil {
pvc = storage.PerWorkspaceStrategyPvcConfig
}
func updateWorkspaceConfig(devEnvironments *chev2.CheClusterDevEnvironments, operatorConfig *controllerv1alpha1.OperatorConfiguration) error {
if operatorConfig.Workspace == nil {
operatorConfig.Workspace = &controllerv1alpha1.WorkspaceConfig{}
}
if err := updateWorkspaceStorageConfig(devEnvironments, operatorConfig.Workspace); err != nil {
return err
}
if err := updateWorkspaceServiceAccountConfig(devEnvironments, operatorConfig.Workspace); err != nil {
return err
}
return nil
}
func updateWorkspaceStorageConfig(devEnvironments *chev2.CheClusterDevEnvironments, workspaceConfig *controllerv1alpha1.WorkspaceConfig) error {
pvcStrategy := utils.GetValue(devEnvironments.Storage.PvcStrategy, constants.DefaultPvcStorageStrategy)
isPerWorkspacePVCStorageStrategy := pvcStrategy == constants.PerWorkspacePVCStorageStrategy
pvc := map[bool]*chev2.PVC{
true: devEnvironments.Storage.PerWorkspaceStrategyPvcConfig,
false: devEnvironments.Storage.PerUserStrategyPvcConfig,
}[isPerWorkspacePVCStorageStrategy]
if pvc != nil {
if operatorConfig.Workspace == nil {
operatorConfig.Workspace = &controllerv1alpha1.WorkspaceConfig{}
}
return updateWorkspaceConfig(pvc, pvcStrategy == constants.PerWorkspacePVCStorageStrategy, operatorConfig.Workspace)
}
return nil
}
func updateWorkspaceConfig(pvc *chev2.PVC, isPerWorkspacePVCStorageStrategy bool, workspaceConfig *controllerv1alpha1.WorkspaceConfig) error {
if pvc.StorageClass != "" {
workspaceConfig.StorageClassName = &pvc.StorageClass
}
if pvc.ClaimSize != "" {
if workspaceConfig.DefaultStorageSize == nil {
workspaceConfig.DefaultStorageSize = &controllerv1alpha1.StorageSizes{}
if pvc.StorageClass != "" {
workspaceConfig.StorageClassName = &pvc.StorageClass
}
pvcSize, err := resource.ParseQuantity(pvc.ClaimSize)
if err != nil {
return err
}
if pvc.ClaimSize != "" {
if workspaceConfig.DefaultStorageSize == nil {
workspaceConfig.DefaultStorageSize = &controllerv1alpha1.StorageSizes{}
}
if isPerWorkspacePVCStorageStrategy {
workspaceConfig.DefaultStorageSize.PerWorkspace = &pvcSize
} else {
workspaceConfig.DefaultStorageSize.Common = &pvcSize
pvcSize, err := resource.ParseQuantity(pvc.ClaimSize)
if err != nil {
return err
}
if isPerWorkspacePVCStorageStrategy {
workspaceConfig.DefaultStorageSize.PerWorkspace = &pvcSize
} else {
workspaceConfig.DefaultStorageSize.Common = &pvcSize
}
}
}
return nil
}
func updateWorkspaceServiceAccountConfig(devEnvironments *chev2.CheClusterDevEnvironments, workspaceConfig *controllerv1alpha1.WorkspaceConfig) error {
isNamespaceAutoProvisioned := pointer.BoolPtrDerefOr(devEnvironments.DefaultNamespace.AutoProvision, constants.DefaultAutoProvision)
workspaceConfig.ServiceAccount = &controllerv1alpha1.ServiceAccountConfig{
ServiceAccountName: devEnvironments.ServiceAccount,
// If user's Namespace is not auto provisioned (is pre-created by admin), then ServiceAccount must be pre-created as well
DisableCreation: pointer.BoolPtr(!isNamespaceAutoProvisioned && devEnvironments.ServiceAccount != ""),
}
return nil
}

View File

@ -15,6 +15,9 @@ import (
"regexp"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/eclipse-che/che-operator/pkg/common/constants"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/utils/pointer"
@ -85,7 +88,7 @@ func TestReconcileDevWorkspaceConfigPerUserStorage(t *testing.T) {
DevEnvironments: chev2.CheClusterDevEnvironments{},
},
},
expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{},
expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{Workspace: &controllerv1alpha1.WorkspaceConfig{}},
},
{
name: "Create DevWorkspaceOperatorConfig with StorageClassName only",
@ -330,7 +333,8 @@ func TestReconcileDevWorkspaceConfigPerUserStorage(t *testing.T) {
err = deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc)
assert.NoError(t, err)
assert.Equal(t, testCase.expectedOperatorConfig, dwoc.Config)
diff := cmp.Diff(testCase.expectedOperatorConfig, dwoc.Config, cmp.Options{cmpopts.IgnoreFields(controllerv1alpha1.WorkspaceConfig{}, "ServiceAccount")})
assert.Empty(t, diff)
})
}
@ -346,3 +350,120 @@ func TestReconcileDevWorkspaceConfigPerUserStorage(t *testing.T) {
})
}
}
func TestReconcileServiceAccountConfig(t *testing.T) {
type testCase struct {
name string
cheCluster *chev2.CheCluster
expectedOperatorConfig *controllerv1alpha1.OperatorConfiguration
}
var testCases = []testCase{
{
name: "Case #1",
cheCluster: &chev2.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Namespace: "eclipse-che",
Name: "eclipse-che",
},
Spec: chev2.CheClusterSpec{
DevEnvironments: chev2.CheClusterDevEnvironments{
ServiceAccount: "service-account",
},
},
},
expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{
Workspace: &controllerv1alpha1.WorkspaceConfig{
ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{
ServiceAccountName: "service-account",
DisableCreation: pointer.BoolPtr(false),
},
},
},
},
{
name: "Case #2",
cheCluster: &chev2.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Namespace: "eclipse-che",
Name: "eclipse-che",
},
Spec: chev2.CheClusterSpec{
DevEnvironments: chev2.CheClusterDevEnvironments{
DefaultNamespace: chev2.DefaultNamespace{
AutoProvision: pointer.BoolPtr(false),
},
ServiceAccount: "service-account",
},
},
},
expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{
Workspace: &controllerv1alpha1.WorkspaceConfig{
ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{
ServiceAccountName: "service-account",
DisableCreation: pointer.BoolPtr(true),
},
},
},
},
{
name: "Case #3",
cheCluster: &chev2.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Namespace: "eclipse-che",
Name: "eclipse-che",
},
Spec: chev2.CheClusterSpec{
DevEnvironments: chev2.CheClusterDevEnvironments{},
},
},
expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{
Workspace: &controllerv1alpha1.WorkspaceConfig{
ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{
DisableCreation: pointer.BoolPtr(false),
},
},
},
},
{
name: "Case #4",
cheCluster: &chev2.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Namespace: "eclipse-che",
Name: "eclipse-che",
},
Spec: chev2.CheClusterSpec{
DevEnvironments: chev2.CheClusterDevEnvironments{
DefaultNamespace: chev2.DefaultNamespace{
AutoProvision: pointer.BoolPtr(false),
},
},
},
},
expectedOperatorConfig: &controllerv1alpha1.OperatorConfiguration{
Workspace: &controllerv1alpha1.WorkspaceConfig{
ServiceAccount: &controllerv1alpha1.ServiceAccountConfig{
DisableCreation: pointer.BoolPtr(false),
},
},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
deployContext := test.GetDeployContext(testCase.cheCluster, []runtime.Object{})
infrastructure.InitializeForTesting(infrastructure.OpenShiftv4)
devWorkspaceConfigReconciler := NewDevWorkspaceConfigReconciler()
_, _, err := devWorkspaceConfigReconciler.Reconcile(deployContext)
assert.NoError(t, err)
dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{}
err = deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: devWorkspaceConfigName, Namespace: testCase.cheCluster.Namespace}, dwoc)
assert.NoError(t, err)
assert.Equal(t, testCase.expectedOperatorConfig.Workspace.ServiceAccount, dwoc.Config.Workspace.ServiceAccount)
})
}
}

View File

@ -0,0 +1,21 @@
package v1alpha2
import attributes "github.com/devfile/api/v2/pkg/attributes"
type ComponentContribution struct {
// Mandatory name that allows referencing the component
// from other elements (such as commands) or from an external
// devfile that may reference this component through a parent or a plugin.
// +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
// +kubebuilder:validation:MaxLength=63
Name string `json:"name"`
// Map of implementation-dependant free-form YAML attributes.
// +optional
// +kubebuilder:validation:Type=object
// +kubebuilder:pruning:PreserveUnknownFields
// +kubebuilder:validation:Schemaless
Attributes attributes.Attributes `json:"attributes,omitempty"`
// Reference to a remote Devfile or DevWorkspace resource to be included
// in the DevWorkspace.
PluginComponent `json:",inline"`
}

View File

@ -7,9 +7,10 @@ import (
// DevWorkspaceSpec defines the desired state of DevWorkspace
type DevWorkspaceSpec struct {
Started bool `json:"started"`
RoutingClass string `json:"routingClass,omitempty"`
Template DevWorkspaceTemplateSpec `json:"template,omitempty"`
Started bool `json:"started"`
RoutingClass string `json:"routingClass,omitempty"`
Template DevWorkspaceTemplateSpec `json:"template,omitempty"`
Contributions []ComponentContribution `json:"contributions,omitempty"`
}
// DevWorkspaceStatus defines the observed state of DevWorkspace

View File

@ -830,6 +830,29 @@ func (in *Component) DeepCopy() *Component {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentContribution) DeepCopyInto(out *ComponentContribution) {
*out = *in
if in.Attributes != nil {
in, out := &in.Attributes, &out.Attributes
*out = make(attributes.Attributes, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
in.PluginComponent.DeepCopyInto(&out.PluginComponent)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentContribution.
func (in *ComponentContribution) DeepCopy() *ComponentContribution {
if in == nil {
return nil
}
out := new(ComponentContribution)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentParentOverride) DeepCopyInto(out *ComponentParentOverride) {
*out = *in
@ -1637,6 +1660,13 @@ func (in *DevWorkspaceList) DeepCopyObject() runtime.Object {
func (in *DevWorkspaceSpec) DeepCopyInto(out *DevWorkspaceSpec) {
*out = *in
in.Template.DeepCopyInto(&out.Template)
if in.Contributions != nil {
in, out := &in.Contributions, &out.Contributions
*out = make([]ComponentContribution, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevWorkspaceSpec.

View File

@ -16,6 +16,7 @@
package v1alpha1
import (
dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -78,6 +79,21 @@ type StorageSizes struct {
PerWorkspace *resource.Quantity `json:"perWorkspace,omitempty"`
}
type ServiceAccountConfig struct {
// ServiceAccountName defines a fixed name to be used for all DevWorkspaces. If set, the DevWorkspace
// Operator will not generate a separate ServiceAccount for each DevWorkspace, and will instead create
// a ServiceAccount with the specified name in each namespace where DevWorkspaces are created. If specified,
// the created ServiceAccount will not be removed when DevWorkspaces are deleted and must be cleaned up manually.
// +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
// +kubebuilder:validation:MaxLength=63
ServiceAccountName string `json:"serviceAccountName,omitempty"`
// Disable creation of DevWorkspace ServiceAccounts by the DevWorkspace Operator. If set to true, the serviceAccountName
// field must also be set. If ServiceAccount creation is disabled, it is assumed that the specified ServiceAccount already
// exists in any namespace where a workspace is created. If a suitable ServiceAccount does not exist, starting DevWorkspaces
// will fail.
DisableCreation *bool `json:"disableCreation,omitempty"`
}
type WorkspaceConfig struct {
// ImagePullPolicy defines the imagePullPolicy used for containers in a DevWorkspace
// For additional information, see Kubernetes documentation for imagePullPolicy. If
@ -94,6 +110,9 @@ type WorkspaceConfig struct {
// +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
// +kubebuilder:validation:MaxLength=63
PVCName string `json:"pvcName,omitempty"`
// ServiceAccount defines configuration options for the ServiceAccount used for
// DevWorkspaces.
ServiceAccount *ServiceAccountConfig `json:"serviceAccount,omitempty"`
// StorageClassName defines an optional storageClass to use for persistent
// volume claims created to support DevWorkspaces
StorageClassName *string `json:"storageClassName,omitempty"`
@ -123,10 +142,17 @@ type WorkspaceConfig struct {
// but the objects will be left on the cluster). The default value is false.
CleanupOnStop *bool `json:"cleanupOnStop,omitempty"`
// PodSecurityContext overrides the default PodSecurityContext used for all workspace-related
// pods created by the DevWorkspace Operator when running on Kubernetes. On OpenShift, this
// configuration option is ignored. If set, the entire pod security context is overridden;
// values are not merged.
// pods created by the DevWorkspace Operator. If set, defined values are merged into the default
// configuration
PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`
// ContainerSecurityContext overrides the default ContainerSecurityContext used for all
// workspace-related containers created by the DevWorkspace Operator. If set, defined
// values are merged into the default configuration
ContainerSecurityContext *corev1.SecurityContext `json:"containerSecurityContext,omitempty"`
// DefaultTemplate defines an optional DevWorkspace Spec Template which gets applied to the workspace
// if the workspace's Template Spec Components are not defined. The DefaultTemplate will overwrite the existing
// Template Spec, with the exception of Projects (if any are defined).
DefaultTemplate *dw.DevWorkspaceTemplateSpecContent `json:"defaultTemplate,omitempty"`
}
// DevWorkspaceOperatorConfig is the Schema for the devworkspaceoperatorconfigs API

View File

@ -1,4 +1,3 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
//
@ -21,6 +20,7 @@
package v1alpha1
import (
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
v1 "k8s.io/api/core/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@ -478,6 +478,26 @@ func (in *RoutingConfig) DeepCopy() *RoutingConfig {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceAccountConfig) DeepCopyInto(out *ServiceAccountConfig) {
*out = *in
if in.DisableCreation != nil {
in, out := &in.DisableCreation, &out.DisableCreation
*out = new(bool)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountConfig.
func (in *ServiceAccountConfig) DeepCopy() *ServiceAccountConfig {
if in == nil {
return nil
}
out := new(ServiceAccountConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StorageSizes) DeepCopyInto(out *StorageSizes) {
*out = *in
@ -506,6 +526,11 @@ func (in *StorageSizes) DeepCopy() *StorageSizes {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkspaceConfig) DeepCopyInto(out *WorkspaceConfig) {
*out = *in
if in.ServiceAccount != nil {
in, out := &in.ServiceAccount, &out.ServiceAccount
*out = new(ServiceAccountConfig)
(*in).DeepCopyInto(*out)
}
if in.StorageClassName != nil {
in, out := &in.StorageClassName, &out.StorageClassName
*out = new(string)
@ -531,6 +556,16 @@ func (in *WorkspaceConfig) DeepCopyInto(out *WorkspaceConfig) {
*out = new(v1.PodSecurityContext)
(*in).DeepCopyInto(*out)
}
if in.ContainerSecurityContext != nil {
in, out := &in.ContainerSecurityContext, &out.ContainerSecurityContext
*out = new(v1.SecurityContext)
(*in).DeepCopyInto(*out)
}
if in.DefaultTemplate != nil {
in, out := &in.DefaultTemplate, &out.DefaultTemplate
*out = new(v1alpha2.DevWorkspaceTemplateSpecContent)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceConfig.

View File

@ -57,7 +57,8 @@ func (s *BasicSolver) Finalize(*controllerv1alpha1.DevWorkspaceRouting) error {
func (s *BasicSolver) GetSpecObjects(routing *controllerv1alpha1.DevWorkspaceRouting, workspaceMeta DevWorkspaceMetadata) (RoutingObjects, error) {
routingObjects := RoutingObjects{}
routingSuffix := config.Routing.ClusterHostSuffix
// TODO: Use workspace-scoped ClusterHostSuffix to allow overriding
routingSuffix := config.GetGlobalConfig().Routing.ClusterHostSuffix
if routingSuffix == "" {
return routingObjects, &RoutingInvalid{"basic routing requires .config.routing.clusterHostSuffix to be set in operator config"}
}

View File

@ -48,8 +48,25 @@ func ServiceName(workspaceId string) string {
return fmt.Sprintf("%s-%s", workspaceId, "service")
}
func ServiceAccountName(workspaceId string) string {
return fmt.Sprintf("%s-%s", workspaceId, "sa")
func ServiceAccountName(workspace *DevWorkspaceWithConfig) string {
if workspace.Config.Workspace.ServiceAccount.ServiceAccountName != "" {
return workspace.Config.Workspace.ServiceAccount.ServiceAccountName
}
return fmt.Sprintf("%s-%s", workspace.Status.DevWorkspaceId, "sa")
}
func ServiceAccountLabels(workspace *DevWorkspaceWithConfig) map[string]string {
if workspace.Config.Workspace.ServiceAccount.ServiceAccountName != "" {
// One SA used for multiple workspaces; do not add specific DevWorkspace ID. We still need the
// devworkspace ID label in order to cache the ServiceAccount.
return map[string]string{
constants.DevWorkspaceIDLabel: "",
}
}
return map[string]string{
constants.DevWorkspaceIDLabel: workspace.Status.DevWorkspaceId,
constants.DevWorkspaceNameLabel: workspace.Name,
}
}
func EndpointHostname(routingSuffix, workspaceId, endpointName string, endpointPort int) string {

View File

@ -0,0 +1,24 @@
// Copyright (c) 2019-2022 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package common
import (
dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
)
type DevWorkspaceWithConfig struct {
*dw.DevWorkspace `json:",inline"`
Config *controllerv1alpha1.OperatorConfiguration `json:"resolvedConfig"`
}

View File

@ -16,9 +16,13 @@
package config
import (
"fmt"
"github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/utils/pointer"
)
// defaultConfig represents the default configuration for the DevWorkspace Operator.
@ -30,28 +34,45 @@ var defaultConfig = &v1alpha1.OperatorConfiguration{
Workspace: &v1alpha1.WorkspaceConfig{
ImagePullPolicy: "Always",
PVCName: "claim-devworkspace",
ServiceAccount: &v1alpha1.ServiceAccountConfig{
DisableCreation: pointer.Bool(false),
},
DefaultStorageSize: &v1alpha1.StorageSizes{
Common: &commonStorageSize,
PerWorkspace: &perWorkspaceStorageSize,
},
IdleTimeout: "15m",
ProgressTimeout: "5m",
CleanupOnStop: &boolFalse,
PodSecurityContext: &corev1.PodSecurityContext{
RunAsUser: &int64UID,
RunAsGroup: &int64GID,
RunAsNonRoot: &boolTrue,
FSGroup: &int64UID,
},
IdleTimeout: "15m",
ProgressTimeout: "5m",
CleanupOnStop: pointer.BoolPtr(false),
PodSecurityContext: nil,
ContainerSecurityContext: &corev1.SecurityContext{},
DefaultTemplate: nil,
},
}
var defaultKubernetesPodSecurityContext = &corev1.PodSecurityContext{
RunAsUser: pointer.Int64(1234),
RunAsGroup: pointer.Int64(0),
RunAsNonRoot: pointer.Bool(true),
FSGroup: pointer.Int64(1234),
}
var defaultOpenShiftPodSecurityContext = &corev1.PodSecurityContext{}
// Necessary variables for setting pointer values
var (
boolTrue = true
boolFalse = false
int64UID = int64(1234)
int64GID = int64(0)
commonStorageSize = resource.MustParse("10Gi")
perWorkspaceStorageSize = resource.MustParse("5Gi")
)
func setDefaultPodSecurityContext() error {
if !infrastructure.IsInitialized() {
return fmt.Errorf("can not set default pod security context, infrastructure not detected")
}
if infrastructure.IsOpenShift() {
defaultConfig.Workspace.PodSecurityContext = defaultOpenShiftPodSecurityContext
} else {
defaultConfig.Workspace.PodSecurityContext = defaultKubernetesPodSecurityContext
}
return nil
}

View File

@ -22,6 +22,7 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
dw "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
@ -128,8 +129,7 @@ func convertConfigMapToConfigCRD(client crclient.Client) (*dw.DevWorkspaceOperat
var experimentalFeatures *bool
experimentalFeaturesStr := configmap.ControllerCfg.GetExperimentalFeaturesEnabled()
if experimentalFeaturesStr != nil && *experimentalFeaturesStr == "true" {
trueBool := true
experimentalFeatures = &trueBool
experimentalFeatures = pointer.Bool(true)
}
if !setRoutingConfig && !setWorkspaceConfig && experimentalFeatures == nil {

View File

@ -17,19 +17,26 @@ package config
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"sync"
dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/devworkspace-operator/pkg/config/proxy"
routeV1 "github.com/openshift/api/route/v1"
corev1 "k8s.io/api/core/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
controller "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
)
@ -39,26 +46,64 @@ const (
)
var (
Routing *controller.RoutingConfig
Workspace *controller.WorkspaceConfig
internalConfig *controller.OperatorConfiguration
configMutex sync.Mutex
configNamespace string
log = ctrl.Log.WithName("operator-configuration")
)
func SetConfigForTesting(config *controller.OperatorConfiguration) {
func GetGlobalConfig() *controller.OperatorConfiguration {
return internalConfig.DeepCopy()
}
// ResolveConfigForWorkspace returns the resulting config from merging the global DevWorkspaceOperatorConfig with the
// DevWorkspaceOperatorConfig specified by the optional workspace attribute `controller.devfile.io/devworkspace-config`.
// If the `controller.devfile.io/devworkspace-config` is not set, the global DevWorkspaceOperatorConfig is returned.
// If the `controller.devfile.io/devworkspace-config` attribute is incorrectly set, or the specified DevWorkspaceOperatorConfig
// does not exist on the cluster, an error is returned.
func ResolveConfigForWorkspace(workspace *dw.DevWorkspace, client crclient.Client) (*controller.OperatorConfiguration, error) {
if !workspace.Spec.Template.Attributes.Exists(constants.ExternalDevWorkspaceConfiguration) {
return GetGlobalConfig(), nil
}
namespacedName := types.NamespacedName{}
err := workspace.Spec.Template.Attributes.GetInto(constants.ExternalDevWorkspaceConfiguration, &namespacedName)
if err != nil {
return nil, fmt.Errorf("failed to read attribute %s in DevWorkspace attributes: %w", constants.ExternalDevWorkspaceConfiguration, err)
}
if namespacedName.Name == "" {
return nil, fmt.Errorf("'name' must be set for attribute %s in DevWorkspace attributes", constants.ExternalDevWorkspaceConfiguration)
}
if namespacedName.Namespace == "" {
return nil, fmt.Errorf("'namespace' must be set for attribute %s in DevWorkspace attributes", constants.ExternalDevWorkspaceConfiguration)
}
externalDWOC := &controller.DevWorkspaceOperatorConfig{}
err = client.Get(context.TODO(), namespacedName, externalDWOC)
if err != nil {
return nil, fmt.Errorf("could not fetch external DWOC with name %s in namespace %s: %w", namespacedName.Name, namespacedName.Namespace, err)
}
return getMergedConfig(externalDWOC.Config, internalConfig), nil
}
func GetConfigForTesting(customConfig *controller.OperatorConfiguration) *controller.OperatorConfiguration {
configMutex.Lock()
defer configMutex.Unlock()
internalConfig = defaultConfig.DeepCopy()
mergeConfig(config, internalConfig)
updatePublicConfig()
testConfig := defaultConfig.DeepCopy()
mergeConfig(customConfig, testConfig)
return testConfig
}
func SetupControllerConfig(client crclient.Client) error {
if internalConfig != nil {
return fmt.Errorf("internal controller configuration is already set up")
}
if err := setDefaultPodSecurityContext(); err != nil {
return err
}
internalConfig = &controller.OperatorConfiguration{}
namespace, err := infrastructure.GetNamespace()
@ -93,7 +138,7 @@ func SetupControllerConfig(client crclient.Client) error {
defaultConfig.Routing.ProxyConfig = clusterProxy
internalConfig.Routing.ProxyConfig = proxy.MergeProxyConfigs(clusterProxy, internalConfig.Routing.ProxyConfig)
updatePublicConfig()
logCurrentConfig()
return nil
}
@ -119,6 +164,13 @@ func getClusterConfig(namespace string, client crclient.Client) (*controller.Dev
return clusterConfig, nil
}
func getMergedConfig(from, to *controller.OperatorConfiguration) *controller.OperatorConfiguration {
mergedConfig := to.DeepCopy()
fromCopy := from.DeepCopy()
mergeConfig(fromCopy, mergedConfig)
return mergedConfig
}
func syncConfigFrom(newConfig *controller.DevWorkspaceOperatorConfig) {
if newConfig == nil || newConfig.Name != OperatorConfigName || newConfig.Namespace != configNamespace {
return
@ -127,19 +179,13 @@ func syncConfigFrom(newConfig *controller.DevWorkspaceOperatorConfig) {
defer configMutex.Unlock()
internalConfig = defaultConfig.DeepCopy()
mergeConfig(newConfig.Config, internalConfig)
updatePublicConfig()
logCurrentConfig()
}
func restoreDefaultConfig() {
configMutex.Lock()
defer configMutex.Unlock()
internalConfig = defaultConfig.DeepCopy()
updatePublicConfig()
}
func updatePublicConfig() {
Routing = internalConfig.Routing.DeepCopy()
Workspace = internalConfig.Workspace.DeepCopy()
logCurrentConfig()
}
@ -222,6 +268,17 @@ func mergeConfig(from, to *controller.OperatorConfiguration) {
if from.Workspace.PVCName != "" {
to.Workspace.PVCName = from.Workspace.PVCName
}
if from.Workspace.ServiceAccount != nil {
if to.Workspace.ServiceAccount == nil {
to.Workspace.ServiceAccount = &controller.ServiceAccountConfig{}
}
if from.Workspace.ServiceAccount.ServiceAccountName != "" {
to.Workspace.ServiceAccount.ServiceAccountName = from.Workspace.ServiceAccount.ServiceAccountName
}
if from.Workspace.ServiceAccount.DisableCreation != nil {
to.Workspace.ServiceAccount.DisableCreation = pointer.BoolPtr(*from.Workspace.ServiceAccount.DisableCreation)
}
}
if from.Workspace.ImagePullPolicy != "" {
to.Workspace.ImagePullPolicy = from.Workspace.ImagePullPolicy
}
@ -238,7 +295,10 @@ func mergeConfig(from, to *controller.OperatorConfiguration) {
to.Workspace.CleanupOnStop = from.Workspace.CleanupOnStop
}
if from.Workspace.PodSecurityContext != nil {
to.Workspace.PodSecurityContext = from.Workspace.PodSecurityContext
to.Workspace.PodSecurityContext = mergePodSecurityContext(to.Workspace.PodSecurityContext, from.Workspace.PodSecurityContext)
}
if from.Workspace.ContainerSecurityContext != nil {
to.Workspace.ContainerSecurityContext = mergeContainerSecurityContext(to.Workspace.ContainerSecurityContext, from.Workspace.ContainerSecurityContext)
}
if from.Workspace.DefaultStorageSize != nil {
if to.Workspace.DefaultStorageSize == nil {
@ -253,57 +313,143 @@ func mergeConfig(from, to *controller.OperatorConfiguration) {
to.Workspace.DefaultStorageSize.PerWorkspace = &perWorkspaceSizeCopy
}
}
if from.Workspace.DefaultTemplate != nil {
templateSpecContentCopy := from.Workspace.DefaultTemplate.DeepCopy()
to.Workspace.DefaultTemplate = templateSpecContentCopy
}
}
}
func mergePodSecurityContext(base, patch *corev1.PodSecurityContext) *corev1.PodSecurityContext {
baseBytes, err := json.Marshal(base)
if err != nil {
log.Info("Failed to serialize base pod security context: %s", err)
return base
}
patchBytes, err := json.Marshal(patch)
if err != nil {
log.Info("Failed to serialize configured pod security context: %s", err)
return base
}
patchedBytes, err := strategicpatch.StrategicMergePatch(baseBytes, patchBytes, &corev1.PodSecurityContext{})
if err != nil {
log.Info("Failed to merge configured pod security context: %s", err)
return base
}
patched := &corev1.PodSecurityContext{}
if err := json.Unmarshal(patchedBytes, patched); err != nil {
log.Info("Failed to deserialize patched pod security context: %s", patched)
return base
}
return patched
}
func mergeContainerSecurityContext(base, patch *corev1.SecurityContext) *corev1.SecurityContext {
baseBytes, err := json.Marshal(base)
if err != nil {
log.Info("Failed to serialize base container security context: %s", err)
return base
}
patchBytes, err := json.Marshal(patch)
if err != nil {
log.Info("Failed to serialize configured container security context: %s", err)
return base
}
patchedBytes, err := strategicpatch.StrategicMergePatch(baseBytes, patchBytes, &corev1.SecurityContext{})
if err != nil {
log.Info("Failed to merge configured container security context: %s", err)
return base
}
patched := &corev1.SecurityContext{}
if err := json.Unmarshal(patchedBytes, patched); err != nil {
log.Info("Failed to deserialize patched container security context: %s", patched)
return base
}
return patched
}
func GetCurrentConfigString(currConfig *controller.OperatorConfiguration) string {
if currConfig == nil {
return ""
}
routing := currConfig.Routing
var config []string
if routing != nil {
if routing.ClusterHostSuffix != "" && routing.ClusterHostSuffix != defaultConfig.Routing.ClusterHostSuffix {
config = append(config, fmt.Sprintf("routing.clusterHostSuffix=%s", routing.ClusterHostSuffix))
}
if routing.DefaultRoutingClass != defaultConfig.Routing.DefaultRoutingClass {
config = append(config, fmt.Sprintf("routing.defaultRoutingClass=%s", routing.DefaultRoutingClass))
}
}
workspace := currConfig.Workspace
if workspace != nil {
if workspace.ImagePullPolicy != defaultConfig.Workspace.ImagePullPolicy {
config = append(config, fmt.Sprintf("workspace.imagePullPolicy=%s", workspace.ImagePullPolicy))
}
if workspace.PVCName != defaultConfig.Workspace.PVCName {
config = append(config, fmt.Sprintf("workspace.pvcName=%s", workspace.PVCName))
}
if workspace.ServiceAccount != nil {
if workspace.ServiceAccount.ServiceAccountName != defaultConfig.Workspace.ServiceAccount.ServiceAccountName {
config = append(config, fmt.Sprintf("workspace.serviceAccount.serviceAccountName=%s", workspace.ServiceAccount.ServiceAccountName))
}
if workspace.ServiceAccount.DisableCreation != nil && *workspace.ServiceAccount.DisableCreation != *defaultConfig.Workspace.ServiceAccount.DisableCreation {
config = append(config, fmt.Sprintf("workspace.serviceAccount.disableCreation=%t", *workspace.ServiceAccount.DisableCreation))
}
}
if workspace.StorageClassName != nil && workspace.StorageClassName != defaultConfig.Workspace.StorageClassName {
config = append(config, fmt.Sprintf("workspace.storageClassName=%s", *workspace.StorageClassName))
}
if workspace.IdleTimeout != defaultConfig.Workspace.IdleTimeout {
config = append(config, fmt.Sprintf("workspace.idleTimeout=%s", workspace.IdleTimeout))
}
if workspace.ProgressTimeout != "" && workspace.ProgressTimeout != defaultConfig.Workspace.ProgressTimeout {
config = append(config, fmt.Sprintf("workspace.progressTimeout=%s", workspace.ProgressTimeout))
}
if workspace.IgnoredUnrecoverableEvents != nil {
config = append(config, fmt.Sprintf("workspace.ignoredUnrecoverableEvents=%s",
strings.Join(workspace.IgnoredUnrecoverableEvents, ";")))
}
if workspace.CleanupOnStop != nil && *workspace.CleanupOnStop != *defaultConfig.Workspace.CleanupOnStop {
config = append(config, fmt.Sprintf("workspace.cleanupOnStop=%t", *workspace.CleanupOnStop))
}
if workspace.DefaultStorageSize != nil {
if workspace.DefaultStorageSize.Common != nil && workspace.DefaultStorageSize.Common.String() != defaultConfig.Workspace.DefaultStorageSize.Common.String() {
config = append(config, fmt.Sprintf("workspace.defaultStorageSize.common=%s", workspace.DefaultStorageSize.Common.String()))
}
if workspace.DefaultStorageSize.PerWorkspace != nil && workspace.DefaultStorageSize.PerWorkspace.String() != defaultConfig.Workspace.DefaultStorageSize.PerWorkspace.String() {
config = append(config, fmt.Sprintf("workspace.defaultStorageSize.perWorkspace=%s", workspace.DefaultStorageSize.PerWorkspace.String()))
}
}
if !reflect.DeepEqual(workspace.PodSecurityContext, defaultConfig.Workspace.PodSecurityContext) {
config = append(config, "workspace.podSecurityContext is set")
}
if !reflect.DeepEqual(workspace.ContainerSecurityContext, defaultConfig.Workspace.ContainerSecurityContext) {
config = append(config, "workspace.containerSecurityContext is set")
}
if workspace.DefaultTemplate != nil {
config = append(config, "workspace.defaultTemplate is set")
}
}
if currConfig.EnableExperimentalFeatures != nil && *currConfig.EnableExperimentalFeatures {
config = append(config, "enableExperimentalFeatures=true")
}
if len(config) == 0 {
return ""
} else {
return strings.Join(config, ",")
}
}
// logCurrentConfig formats the current operator configuration as a plain string
func logCurrentConfig() {
if internalConfig == nil {
return
}
var config []string
if Routing != nil {
if Routing.ClusterHostSuffix != "" && Routing.ClusterHostSuffix != defaultConfig.Routing.ClusterHostSuffix {
config = append(config, fmt.Sprintf("routing.clusterHostSuffix=%s", Routing.ClusterHostSuffix))
}
if Routing.DefaultRoutingClass != defaultConfig.Routing.DefaultRoutingClass {
config = append(config, fmt.Sprintf("routing.defaultRoutingClass=%s", Routing.DefaultRoutingClass))
}
}
if Workspace != nil {
if Workspace.ImagePullPolicy != defaultConfig.Workspace.ImagePullPolicy {
config = append(config, fmt.Sprintf("workspace.imagePullPolicy=%s", Workspace.ImagePullPolicy))
}
if Workspace.PVCName != defaultConfig.Workspace.PVCName {
config = append(config, fmt.Sprintf("workspace.pvcName=%s", Workspace.PVCName))
}
if Workspace.StorageClassName != nil && Workspace.StorageClassName != defaultConfig.Workspace.StorageClassName {
config = append(config, fmt.Sprintf("workspace.storageClassName=%s", *Workspace.StorageClassName))
}
if Workspace.IdleTimeout != defaultConfig.Workspace.IdleTimeout {
config = append(config, fmt.Sprintf("workspace.idleTimeout=%s", Workspace.IdleTimeout))
}
if Workspace.IgnoredUnrecoverableEvents != nil {
config = append(config, fmt.Sprintf("workspace.ignoredUnrecoverableEvents=%s",
strings.Join(Workspace.IgnoredUnrecoverableEvents, ";")))
}
if Workspace.DefaultStorageSize != nil {
if Workspace.DefaultStorageSize.Common != nil {
config = append(config, fmt.Sprintf("workspace.defaultStorageSize.common=%s", Workspace.DefaultStorageSize.Common.String()))
}
if Workspace.DefaultStorageSize.PerWorkspace != nil {
config = append(config, fmt.Sprintf("workspace.defaultStorageSize.perWorkspace=%s", Workspace.DefaultStorageSize.PerWorkspace.String()))
}
}
}
if internalConfig.EnableExperimentalFeatures != nil && *internalConfig.EnableExperimentalFeatures {
config = append(config, "enableExperimentalFeatures=true")
}
if len(config) == 0 {
currConfig := GetCurrentConfigString(internalConfig)
if len(currConfig) == 0 {
log.Info("Updated config to [(default config)]")
} else {
log.Info(fmt.Sprintf("Updated config to [%s]", strings.Join(config, ",")))
log.Info(fmt.Sprintf("Updated config to [%s]", currConfig))
}
if internalConfig.Routing.ProxyConfig != nil {

View File

@ -28,6 +28,21 @@ const (
// stopped.
DevWorkspaceStorageTypeAttribute = "controller.devfile.io/storage-type"
// ExternalDevWorkspaceConfiguration is an attribute that allows for specifying an (optional) external DevWorkspaceOperatorConfig
// which will merged with the internal/global DevWorkspaceOperatorConfig. The DevWorkspaceOperatorConfig resulting from the merge will be used for the workspace.
// The fields which are set in the external DevWorkspaceOperatorConfig will overwrite those existing in the
// internal/global DevWorkspaceOperatorConfig during the merge.
// The structure of the attribute value should contain two strings: name and namespace.
// 'name' specifies the metadata.name of the external operator configuration.
// 'namespace' specifies the metadata.namespace of the external operator configuration .
// For example:
//
// attributes:
// controller.devfile.io/devworkspace-config:
// name: external-dwoc-name
// namespace: some-namespace
ExternalDevWorkspaceConfiguration = "controller.devfile.io/devworkspace-config"
// RuntimeClassNameAttribute is an attribute added to a DevWorkspace to specify a runtimeClassName for container
// components in the DevWorkspace (pod.spec.runtimeClassName). If empty, no runtimeClassName is added.
RuntimeClassNameAttribute = "controller.devfile.io/runtime-class"
@ -74,4 +89,56 @@ const (
// EndpointURLAttribute is an attribute added to endpoints to denote the endpoint on the cluster that
// was created to route to this endpoint
EndpointURLAttribute = "controller.devfile.io/endpoint-url"
// ContainerContributionAttribute defines a container component as a container contribution that should be merged
// into an existing container in the devfile if possible. If no suitable container exists, this component
// is treated as a regular container component
ContainerContributionAttribute = "controller.devfile.io/container-contribution"
// MergeContributionAttribute defines a container component as a target for merging a container contribution. If
// present on a container component, any container contributions will be merged into that container. If multiple
// container components have the merge-contribution attribute, the first one will be used and all others ignored.
MergeContributionAttribute = "controller.devfile.io/merge-contribution"
// MergedContributionsAttribute is applied as an attribute onto a component to list the components from the unflattened
// DevWorkspace that have been merged into the current component. The contributions are listed in a comma-separated list.
MergedContributionsAttribute = "controller.devfile.io/merged-contributions"
// PodOverridesAttribute is an attribute applied to a container component or in global attributes to specify overrides
// for the pod spec used in the main workspace deployment. The format of the field is the same as the Kubernetes
// PodSpec API. Overrides are applied over the default pod template spec used via strategic merge patch.
//
// If this attribute is used multiple times, all overrides are applied in the order they are defined in the DevWorkspace,
// with later values overriding previous ones. Overrides defined in the top-level attributes field are applied last and
// override any overrides from container components.
//
// Example:
// kind: DevWorkspace
// apiVersion: workspace.devfile.io/v1alpha2
// spec:
// template:
// attributes:
// pod-overrides:
// metadata:
// annotations:
// io.openshift.userns: "true"
// io.kubernetes.cri-o.userns-mode: "auto:size=65536;map-to-root=true" # <-- user namespace
// openshift.io/scc: container-build
// spec:
// runtimeClassName: kata
// schedulerName: stork
PodOverridesAttribute = "pod-overrides"
// ContainerOverridesAttribute is an attribute applied to a container component to specify arbitrary fields in that
// container. This attribute should only be used to set fields that are not configurable in the container component
// itself. Any values specified in the overrides attribute overwrite fields on the container.
//
// Example:
// components:
// - name: go
// attributes:
// container-overrides: {"resources":{"limits":{"nvidia.com/gpu": "1"}}}
// container:
// image: ...
ContainerOverridesAttribute = "container-overrides"
)

View File

@ -64,9 +64,12 @@ const (
// Constants describing storage classes supported by the controller
// CommonStorageClassType defines the 'common' storage policy -- one PVC is provisioned per namespace and all devworkspace storage
// is mounted in it on subpaths according to devworkspace ID.
// CommonStorageClassType defines the 'common' storage policy, which is an alias of the 'per-user' storage policy, and operates in the same fashion as the 'per-user' storage policy.
// The 'common' storage policy exists only for legacy compatibility.
CommonStorageClassType = "common"
// PerUserStorageClassType defines the 'per-user' storage policy -- one PVC is provisioned per namespace and all devworkspace storage
// is mounted in it on subpaths according to devworkspace ID.
PerUserStorageClassType = "per-user"
// AsyncStorageClassType defines the 'asynchronous' storage policy. An rsync sidecar is added to devworkspaces that uses SSH to connect
// to a storage deployment that mounts a common PVC for the namespace.
AsyncStorageClassType = "async"

View File

@ -60,6 +60,10 @@ func InitializeForTesting(currentInfrastructure Type) {
initialized = true
}
func IsInitialized() bool {
return initialized
}
// IsOpenShift returns true if the current cluster is an OpenShift (v4.x) cluster.
func IsOpenShift() bool {
if !initialized {

View File

@ -27,6 +27,7 @@ import (
networkingv1 "k8s.io/api/networking/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
)
@ -37,7 +38,7 @@ type diffFunc func(spec crclient.Object, cluster crclient.Object) (delete, updat
var diffFuncs = map[reflect.Type]diffFunc{
reflect.TypeOf(rbacv1.Role{}): allDiffFuncs(labelsAndAnnotationsDiffFunc, basicDiffFunc(roleDiffOpts)),
reflect.TypeOf(rbacv1.RoleBinding{}): allDiffFuncs(labelsAndAnnotationsDiffFunc, basicDiffFunc(rolebindingDiffOpts)),
reflect.TypeOf(corev1.ServiceAccount{}): labelsAndAnnotationsDiffFunc,
reflect.TypeOf(corev1.ServiceAccount{}): allDiffFuncs(labelsAndAnnotationsDiffFunc, ownerrefsDiffFunc),
reflect.TypeOf(appsv1.Deployment{}): allDiffFuncs(deploymentDiffFunc, labelsAndAnnotationsDiffFunc, basicDiffFunc(deploymentDiffOpts)),
reflect.TypeOf(corev1.ConfigMap{}): allDiffFuncs(labelsAndAnnotationsDiffFunc, basicDiffFunc(configmapDiffOpts)),
reflect.TypeOf(corev1.Secret{}): allDiffFuncs(labelsAndAnnotationsDiffFunc, basicDiffFunc(secretDiffOpts)),
@ -73,6 +74,16 @@ func labelsAndAnnotationsDiffFunc(spec, cluster crclient.Object) (delete, update
return false, false
}
func ownerrefsDiffFunc(spec, cluster crclient.Object) (delete, update bool) {
clusterRefs := cluster.GetOwnerReferences()
for _, ownerref := range spec.GetOwnerReferences() {
if !containsOwnerRef(ownerref, clusterRefs) {
return false, true
}
}
return false, false
}
// allDiffFuncs represents an 'and' condition across specified diffFuncs. Functions are checked in provided order,
// returning the result of the first function to require an update/deletion.
func allDiffFuncs(funcs ...diffFunc) diffFunc {
@ -135,3 +146,26 @@ func serviceDiffFunc(spec, cluster crclient.Object) (delete, update bool) {
}
return false, specCopy.Spec.Type != clusterCopy.Spec.Type
}
func containsOwnerRef(toCheck metav1.OwnerReference, listRefs []metav1.OwnerReference) bool {
boolPtrsEqual := func(a, b *bool) bool {
// If either is nil, assume check other is nil or false; otherwise, compare actual values
switch {
case a == nil:
return b == nil || !*b
case b == nil:
return a == nil || !*a
default:
return *a == *b
}
}
for _, ref := range listRefs {
if toCheck.Kind == ref.Kind &&
toCheck.Name == ref.Name &&
toCheck.UID == ref.UID &&
boolPtrsEqual(toCheck.Controller, ref.Controller) {
return true
}
}
return false
}

View File

@ -82,7 +82,7 @@ func createObjectGeneric(specObj crclient.Object, api ClusterAPI) error {
// Need to try to update the object to address an edge case where removing a labelselector
// results in the object not being tracked by the controller's cache.
return updateObjectGeneric(specObj, nil, api)
case k8sErrors.IsInvalid(err):
case k8sErrors.IsInvalid(err), k8sErrors.IsForbidden(err):
return &UnrecoverableSyncError{err}
default:
return err
@ -108,7 +108,7 @@ func updateObjectGeneric(specObj, clusterObj crclient.Object, api ClusterAPI) er
case k8sErrors.IsConflict(err), k8sErrors.IsNotFound(err):
// Need to catch IsNotFound here because we attempt to update when creation fails with AlreadyExists
return NewNotInSync(specObj, NeedRetryReason)
case k8sErrors.IsInvalid(err):
case k8sErrors.IsInvalid(err), k8sErrors.IsForbidden(err):
return &UnrecoverableSyncError{err}
default:
return err

View File

@ -50,11 +50,29 @@ func serviceUpdateFunc(spec, cluster crclient.Object) (crclient.Object, error) {
return specService, nil
}
func serviceAccountUpdateFunc(spec, cluster crclient.Object) (crclient.Object, error) {
if cluster == nil {
// May occur if ServiceAccount is not cached by the operator
return spec, nil
}
spec.SetResourceVersion(cluster.GetResourceVersion())
ownerrefs := spec.GetOwnerReferences()
for _, clusterOwnerref := range cluster.GetOwnerReferences() {
if !containsOwnerRef(clusterOwnerref, ownerrefs) {
ownerrefs = append(ownerrefs, clusterOwnerref)
}
}
spec.SetOwnerReferences(ownerrefs)
return spec, nil
}
func getUpdateFunc(obj crclient.Object) updateFunc {
objType := reflect.TypeOf(obj).Elem()
switch objType {
case reflect.TypeOf(corev1.Service{}):
return serviceUpdateFunc
case reflect.TypeOf(corev1.ServiceAccount{}):
return serviceAccountUpdateFunc
default:
return defaultUpdateFunc
}

View File

@ -46,86 +46,182 @@ func AllPtrFieldsNil(obj interface{}) bool {
return true
}
// Int32Ptr returns a pointer to an int32
func Int32Ptr(i int32) *int32 {
// Int32 returns a pointer to an int32.
func Int32(i int32) *int32 {
return &i
}
// Int32PtrDerefOr dereference the int32 ptr and returns it if not nil,
// else returns def.
func Int32PtrDerefOr(ptr *int32, def int32) int32 {
var Int32Ptr = Int32 // for back-compat
// Int32Deref dereferences the int32 ptr and returns it if not nil, or else
// returns def.
func Int32Deref(ptr *int32, def int32) int32 {
if ptr != nil {
return *ptr
}
return def
}
// Int64Ptr returns a pointer to an int64
func Int64Ptr(i int64) *int64 {
var Int32PtrDerefOr = Int32Deref // for back-compat
// Int32Equal returns true if both arguments are nil or both arguments
// dereference to the same value.
func Int32Equal(a, b *int32) bool {
if (a == nil) != (b == nil) {
return false
}
if a == nil {
return true
}
return *a == *b
}
// Int64 returns a pointer to an int64.
func Int64(i int64) *int64 {
return &i
}
// Int64PtrDerefOr dereference the int64 ptr and returns it if not nil,
// else returns def.
func Int64PtrDerefOr(ptr *int64, def int64) int64 {
var Int64Ptr = Int64 // for back-compat
// Int64Deref dereferences the int64 ptr and returns it if not nil, or else
// returns def.
func Int64Deref(ptr *int64, def int64) int64 {
if ptr != nil {
return *ptr
}
return def
}
// BoolPtr returns a pointer to a bool
func BoolPtr(b bool) *bool {
var Int64PtrDerefOr = Int64Deref // for back-compat
// Int64Equal returns true if both arguments are nil or both arguments
// dereference to the same value.
func Int64Equal(a, b *int64) bool {
if (a == nil) != (b == nil) {
return false
}
if a == nil {
return true
}
return *a == *b
}
// Bool returns a pointer to a bool.
func Bool(b bool) *bool {
return &b
}
// BoolPtrDerefOr dereference the bool ptr and returns it if not nil,
// else returns def.
func BoolPtrDerefOr(ptr *bool, def bool) bool {
var BoolPtr = Bool // for back-compat
// BoolDeref dereferences the bool ptr and returns it if not nil, or else
// returns def.
func BoolDeref(ptr *bool, def bool) bool {
if ptr != nil {
return *ptr
}
return def
}
// StringPtr returns a pointer to the passed string.
func StringPtr(s string) *string {
var BoolPtrDerefOr = BoolDeref // for back-compat
// BoolEqual returns true if both arguments are nil or both arguments
// dereference to the same value.
func BoolEqual(a, b *bool) bool {
if (a == nil) != (b == nil) {
return false
}
if a == nil {
return true
}
return *a == *b
}
// String returns a pointer to a string.
func String(s string) *string {
return &s
}
// StringPtrDerefOr dereference the string ptr and returns it if not nil,
// else returns def.
func StringPtrDerefOr(ptr *string, def string) string {
var StringPtr = String // for back-compat
// StringDeref dereferences the string ptr and returns it if not nil, or else
// returns def.
func StringDeref(ptr *string, def string) string {
if ptr != nil {
return *ptr
}
return def
}
// Float32Ptr returns a pointer to the passed float32.
func Float32Ptr(i float32) *float32 {
var StringPtrDerefOr = StringDeref // for back-compat
// StringEqual returns true if both arguments are nil or both arguments
// dereference to the same value.
func StringEqual(a, b *string) bool {
if (a == nil) != (b == nil) {
return false
}
if a == nil {
return true
}
return *a == *b
}
// Float32 returns a pointer to the a float32.
func Float32(i float32) *float32 {
return &i
}
// Float32PtrDerefOr dereference the float32 ptr and returns it if not nil,
// else returns def.
func Float32PtrDerefOr(ptr *float32, def float32) float32 {
var Float32Ptr = Float32
// Float32Deref dereferences the float32 ptr and returns it if not nil, or else
// returns def.
func Float32Deref(ptr *float32, def float32) float32 {
if ptr != nil {
return *ptr
}
return def
}
// Float64Ptr returns a pointer to the passed float64.
func Float64Ptr(i float64) *float64 {
var Float32PtrDerefOr = Float32Deref // for back-compat
// Float32Equal returns true if both arguments are nil or both arguments
// dereference to the same value.
func Float32Equal(a, b *float32) bool {
if (a == nil) != (b == nil) {
return false
}
if a == nil {
return true
}
return *a == *b
}
// Float64 returns a pointer to the a float64.
func Float64(i float64) *float64 {
return &i
}
// Float64PtrDerefOr dereference the float64 ptr and returns it if not nil,
// else returns def.
func Float64PtrDerefOr(ptr *float64, def float64) float64 {
var Float64Ptr = Float64
// Float64Deref dereferences the float64 ptr and returns it if not nil, or else
// returns def.
func Float64Deref(ptr *float64, def float64) float64 {
if ptr != nil {
return *ptr
}
return def
}
var Float64PtrDerefOr = Float64Deref // for back-compat
// Float64Equal returns true if both arguments are nil or both arguments
// dereference to the same value.
func Float64Equal(a, b *float64) bool {
if (a == nil) != (b == nil) {
return false
}
if a == nil {
return true
}
return *a == *b
}

2
vendor/k8s.io/utils/trace/trace.go generated vendored
View File

@ -56,7 +56,7 @@ func writeTraceItemSummary(b *bytes.Buffer, msg string, totalTime time.Duration,
b.WriteString(" ")
}
b.WriteString(fmt.Sprintf("%vms (%v)", durationToMilliseconds(totalTime), startTime.Format("15:04:00.000")))
b.WriteString(fmt.Sprintf("%vms (%v)", durationToMilliseconds(totalTime), startTime.Format("15:04:05.000")))
}
func durationToMilliseconds(timeDuration time.Duration) int64 {

11
vendor/modules.txt vendored
View File

@ -30,12 +30,12 @@ github.com/cespare/xxhash/v2
github.com/che-incubator/kubernetes-image-puller-operator/api/v1alpha1
# github.com/davecgh/go-spew v1.1.1 => github.com/davecgh/go-spew v1.1.1
github.com/davecgh/go-spew/spew
# github.com/devfile/api/v2 v2.0.0-20220414122024-32cae1f8e42c
# github.com/devfile/api/v2 v2.0.0-20220928161623-fe7c10eaa530
## explicit
github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2
github.com/devfile/api/v2/pkg/attributes
github.com/devfile/api/v2/pkg/devfile
# github.com/devfile/devworkspace-operator v0.15.2
# github.com/devfile/devworkspace-operator v0.17.0
## explicit
github.com/devfile/devworkspace-operator/apis/controller/v1alpha1
github.com/devfile/devworkspace-operator/controllers/controller/devworkspacerouting
@ -67,7 +67,7 @@ github.com/golang/groupcache/lru
# github.com/golang/mock v1.5.0 => github.com/golang/mock v1.5.0
## explicit
github.com/golang/mock/gomock
# github.com/golang/protobuf v1.4.3 => github.com/golang/protobuf v1.4.3
# github.com/golang/protobuf v1.5.2 => github.com/golang/protobuf v1.4.3
github.com/golang/protobuf/proto
github.com/golang/protobuf/ptypes
github.com/golang/protobuf/ptypes/any
@ -296,7 +296,7 @@ google.golang.org/grpc/serviceconfig
google.golang.org/grpc/stats
google.golang.org/grpc/status
google.golang.org/grpc/tap
# google.golang.org/protobuf v1.26.0-rc.1 => google.golang.org/protobuf v1.25.0
# google.golang.org/protobuf v1.26.0 => google.golang.org/protobuf v1.25.0
google.golang.org/protobuf/encoding/prototext
google.golang.org/protobuf/encoding/protowire
google.golang.org/protobuf/internal/descfmt
@ -648,7 +648,7 @@ k8s.io/component-base/config/v1alpha1
k8s.io/klog/v2
# k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 => k8s.io/kube-openapi v0.0.0-20200923105717-7eba4cbaebdf
k8s.io/kube-openapi/pkg/util/proto
# k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 => k8s.io/utils v0.0.0-20201110183641-67b214c5f920
# k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471
## explicit
k8s.io/utils/buffer
k8s.io/utils/integer
@ -1064,7 +1064,6 @@ sigs.k8s.io/yaml
# k8s.io/klog/v2 => k8s.io/klog/v2 v2.8.0
# k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.0.0-20180912235703-14b8d2d93fcb
# k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20200923105717-7eba4cbaebdf
# k8s.io/utils => k8s.io/utils v0.0.0-20201110183641-67b214c5f920
# kubernetes/klog => kubernetes/klog v1.0.0
# modernc.org/b => modernc.org/b v1.0.0
# modernc.org/db => modernc.org/db v1.0.0