feat: Disable plugin registry if openVSX registry is configured (#1843)

* feat: Disable plugin registry if openVSX registry is configured

Signed-off-by: Anatolii Bazko <abazko@redhat.com>
pull/1827/merge
Anatolii Bazko 2024-06-04 10:42:53 +02:00 committed by GitHub
parent 84673c8514
commit 618ad1f1a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 349 additions and 257 deletions

View File

@ -78,7 +78,7 @@ jobs:
registry: quay.io
- name: Build catalog source
run: |
./build/scripts/release/editors-definitions.sh add-env-vars
./build/scripts/release/editors-definitions.sh update-manager-yaml
make update-dev-resources
./build/scripts/release/addDigests.sh -s $(make csv-path CHANNEL=next) -t next
./build/scripts/olm/release-catalog.sh \

View File

@ -202,6 +202,102 @@ metadata:
kubernetes.io/metadata.name: ${USER_NAMESPACE}
EOF
kubectl apply -f - <<EOF
apiVersion: workspace.devfile.io/v1alpha2
kind: DevWorkspaceTemplate
metadata:
name: che-code
namespace: ${USER_NAMESPACE}
spec:
commands:
- apply:
component: che-code-injector
id: init-container-command
- exec:
commandLine: nohup /checode/entrypoint-volume.sh > /checode/entrypoint-logs.txt
2>&1 &
component: che-code-runtime-description
id: init-che-code-command
components:
- container:
command:
- /entrypoint-init-container.sh
cpuLimit: 500m
cpuRequest: 30m
env:
- name: CHE_DASHBOARD_URL
value: https://$(minikube ip).nip.io
- name: OPENVSX_REGISTRY_URL
value: https://open-vsx.org
image: quay.io/che-incubator/che-code:latest
memoryLimit: 256Mi
memoryRequest: 32Mi
sourceMapping: /projects
volumeMounts:
- name: checode
path: /checode
name: che-code-injector
- attributes:
app.kubernetes.io/component: che-code-runtime
app.kubernetes.io/part-of: che-code.eclipse.org
controller.devfile.io/container-contribution: true
container:
cpuLimit: 500m
cpuRequest: 30m
endpoints:
- attributes:
cookiesAuthEnabled: true
discoverable: false
type: main
urlRewriteSupported: true
exposure: public
name: che-code
protocol: https
secure: true
targetPort: 3100
- attributes:
discoverable: false
urlRewriteSupported: false
exposure: public
name: code-redirect-1
protocol: https
targetPort: 13131
- attributes:
discoverable: false
urlRewriteSupported: false
exposure: public
name: code-redirect-2
protocol: https
targetPort: 13132
- attributes:
discoverable: false
urlRewriteSupported: false
exposure: public
name: code-redirect-3
protocol: https
targetPort: 13133
env:
- name: CHE_DASHBOARD_URL
value: https://$(minikube ip).nip.io
- name: OPENVSX_REGISTRY_URL
value: https://open-vsx.org
image: quay.io/devfile/universal-developer-image:ubi8-latest
memoryLimit: 1024Mi
memoryRequest: 256Mi
sourceMapping: /projects
volumeMounts:
- name: checode
path: /checode
name: che-code-runtime-description
- name: checode
volume: {}
events:
postStart:
- init-che-code-command
preStart:
- init-container-command
EOF
kubectl apply -f - <<EOF
kind: DevWorkspace
apiVersion: workspace.devfile.io/v1alpha2
@ -209,11 +305,12 @@ metadata:
name: ${DEV_WORKSPACE_NAME}
namespace: ${USER_NAMESPACE}
spec:
contributions:
- kubernetes:
name: che-code
name: editor
routingClass: che
started: false
contributions:
- name: ide
uri: http://plugin-registry.eclipse-che.svc:8080/v3/plugins/che-incubator/che-code/insiders/devfile.yaml
template:
attributes:
controller.devfile.io/storage-type: ephemeral
@ -229,6 +326,7 @@ EOF
startAndWaitDevWorkspace() {
# pre-pull image for faster workspace startup
minikube image pull quay.io/devfile/universal-developer-image:ubi8-latest
minikube image pull quay.io/che-incubator/che-code:latest
kubectl patch devworkspace ${DEV_WORKSPACE_NAME} -p '{"spec":{"started":true}}' --type=merge -n ${USER_NAMESPACE}
kubectl wait devworkspace ${DEV_WORKSPACE_NAME} -n ${USER_NAMESPACE} --for=jsonpath='{.status.phase}'=Running --timeout=300s

View File

@ -53,7 +53,6 @@ runTest() {
popd
# Free up some resources
minikube image rm quay.io/eclipse/che-plugin-registry:${LAST_PACKAGE_VERSION}
minikube image rm quay.io/eclipse/che-dashboard:${LAST_PACKAGE_VERSION}
minikube image rm quay.io/eclipse/che-server:${LAST_PACKAGE_VERSION}
minikube image rm quay.io/eclipse/che-operator:${LAST_PACKAGE_VERSION}

View File

@ -48,7 +48,6 @@ runTest() {
popd
# Free up some resources
minikube image rm quay.io/eclipse/che-plugin-registry:${PREVIOUS_PACKAGE_VERSION}
minikube image rm quay.io/eclipse/che-dashboard:${PREVIOUS_PACKAGE_VERSION}
minikube image rm quay.io/eclipse/che-server:${PREVIOUS_PACKAGE_VERSION}
minikube image rm quay.io/eclipse/che-operator:${PREVIOUS_PACKAGE_VERSION}

View File

@ -37,17 +37,15 @@ usage () {
echo
echo "Usage:"
echo -e "\t$0 release --version RELEASE_VERSION"
echo -e "\t$0 add-env-vars"
echo -e "\t$0 update-manager-yaml"
}
release() {
if [[ ! ${VERSION} ]]; then usage; exit 1; fi
yq -riY ".metadata.attributes.version = \"${VERSION}\"" "${EDITORS_DEFINITIONS_DIR}/che-code-latest.yaml"
yq -riY "(.components[] | select(.name==\"che-code-injector\") | .container.image) = \"quay.io/che-incubator/che-code:${VERSION}\"" "${EDITORS_DEFINITIONS_DIR}/che-code-latest.yaml"
}
addEnvVars() {
updateManagerYaml() {
for EDITOR_DEFINITION_FILE in $(find "${EDITORS_DEFINITIONS_DIR}" -name "*.yaml"); do
NAME=$(yq -r '.metadata.name' "${EDITOR_DEFINITION_FILE}")
VERSION=$(yq -r '.metadata.attributes.version' "${EDITOR_DEFINITION_FILE}")
@ -74,7 +72,7 @@ init "$@"
pushd "${OPERATOR_REPO}" >/dev/null
case $COMMAND in
'release') release;;
'add-env-vars') addEnvVars;;
'update-manager-yaml'|'add-env-vars') updateManagerYaml;;
*) usage; exit 1;;
esac
popd >/dev/null

View File

@ -183,10 +183,12 @@ releaseEditorsDefinitions() {
echo "[INFO] Releasing editor definitions"
. "${OPERATOR_REPO}/build/scripts/release/editors-definitions.sh" release --version "${RELEASE}"
. "${OPERATOR_REPO}/build/scripts/release/editors-definitions.sh" add-env-vars
. "${OPERATOR_REPO}/build/scripts/release/editors-definitions.sh" update-manager-yaml
make bundle CHANNEL=stable
git add editors-definitions
git add "${OPERATOR_REPO}/config/manager/manager.yaml"
git add "${OPERATOR_REPO}/bundle/stable"
git commit -m "ci: Release editors definitions to $RELEASE" --signoff
}

View File

@ -46,6 +46,7 @@ metadata:
}
],
"externalDevfileRegistry": true,
"externalPluginRegistry": true,
"workspaceNamespaceDefault": "<username>-che"
},
"storage": {
@ -69,6 +70,9 @@ metadata:
"url": "https://registry.devfile.io"
}
]
},
"pluginRegistry": {
"disableInternalRegistry": true
}
},
"containerRegistry": {},
@ -100,7 +104,7 @@ metadata:
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
repository: https://github.com/eclipse-che/che-operator
support: Eclipse Foundation
name: eclipse-che.v7.87.0-870.next
name: eclipse-che.v7.87.0-871.next
namespace: placeholder
spec:
apiservicedefinitions: {}
@ -1032,7 +1036,7 @@ spec:
minKubeVersion: 1.19.0
provider:
name: Eclipse Foundation
version: 7.87.0-870.next
version: 7.87.0-871.next
webhookdefinitions:
- admissionReviewVersions:
- v1

View File

@ -18,6 +18,7 @@ metadata:
spec:
server:
workspaceNamespaceDefault: '<username>-che'
externalPluginRegistry: true
externalDevfileRegistry: true
externalDevfileRegistries:
- url: 'https://registry.devfile.io'

View File

@ -17,6 +17,8 @@ metadata:
namespace: eclipse-che
spec:
components:
pluginRegistry:
disableInternalRegistry: true
devfileRegistry:
disableInternalRegistry: true
externalDevfileRegistries:

View File

@ -15,6 +15,8 @@ package che
import (
"context"
editorsdefinitions "github.com/eclipse-che/che-operator/pkg/deploy/editors-definitions"
"github.com/eclipse-che/che-operator/pkg/common/test"
containerbuild "github.com/eclipse-che/che-operator/pkg/deploy/container-build"
@ -110,6 +112,7 @@ func NewReconciler(
}
reconcileManager.RegisterReconciler(devfileregistry.NewDevfileRegistryReconciler())
reconcileManager.RegisterReconciler(pluginregistry.NewPluginRegistryReconciler())
reconcileManager.RegisterReconciler(editorsdefinitions.NewEditorsDefinitionsReconciler())
reconcileManager.RegisterReconciler(dashboard.NewDashboardReconciler())
reconcileManager.RegisterReconciler(gateway.NewGatewayReconciler())
reconcileManager.RegisterReconciler(server.NewCheServerReconciler())

View File

@ -17,6 +17,8 @@ metadata:
namespace: eclipse-che
spec:
components:
pluginRegistry:
disableInternalRegistry: true
devfileRegistry:
disableInternalRegistry: true
externalDevfileRegistries:

View File

@ -17,6 +17,8 @@ metadata:
namespace: eclipse-che
spec:
components:
pluginRegistry:
disableInternalRegistry: true
devfileRegistry:
disableInternalRegistry: true
externalDevfileRegistries:

View File

@ -17,6 +17,8 @@ metadata:
namespace: eclipse-che
spec:
components:
pluginRegistry:
disableInternalRegistry: true
devfileRegistry:
disableInternalRegistry: true
externalDevfileRegistries:

View File

@ -122,8 +122,8 @@ func (cb *ContainerBuildReconciler) syncSCC(ctx *chetypes.DeployContext) (bool,
})
}
} else {
// Create a new SCC. If custom SCC exists then it won't be touched.
return deploy.Create(ctx, cb.getSccSpec(ctx))
// Create a new SCC.
return deploy.CreateIgnoreIfExists(ctx, cb.getSccSpec(ctx))
}
return true, nil

View File

@ -10,7 +10,7 @@
// Red Hat, Inc. - initial API and implementation
//
package pluginregistry
package editorsdefinitions
import (
"fmt"
@ -18,21 +18,47 @@ import (
"path/filepath"
"regexp"
"github.com/eclipse-che/che-operator/pkg/common/chetypes"
"github.com/eclipse-che/che-operator/pkg/common/constants"
"github.com/eclipse-che/che-operator/pkg/common/utils"
"github.com/eclipse-che/che-operator/pkg/deploy"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/yaml"
"github.com/eclipse-che/che-operator/pkg/common/chetypes"
"github.com/eclipse-che/che-operator/pkg/common/constants"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/eclipse-che/che-operator/pkg/deploy"
)
var (
editorsDefinitionsDir = "/tmp/editors-definitions"
editorsDefinitionsConfigMapName = "editors-definitions"
logger = ctrl.Log.WithName("editorsdefinitions")
)
func (p *PluginRegistryReconciler) syncEditors(ctx *chetypes.DeployContext) (bool, error) {
type EditorsDefinitionsReconciler struct {
deploy.Reconcilable
}
func NewEditorsDefinitionsReconciler() *EditorsDefinitionsReconciler {
return &EditorsDefinitionsReconciler{}
}
func (p *EditorsDefinitionsReconciler) Reconcile(ctx *chetypes.DeployContext) (reconcile.Result, bool, error) {
done, err := p.syncEditors(ctx)
if !done {
return reconcile.Result{}, false, err
}
return reconcile.Result{}, true, nil
}
func (p *EditorsDefinitionsReconciler) Finalize(ctx *chetypes.DeployContext) bool {
return true
}
func (p *EditorsDefinitionsReconciler) syncEditors(ctx *chetypes.DeployContext) (bool, error) {
editorDefinitions, err := readEditorDefinitions()
if err != nil {
return false, err
@ -66,11 +92,11 @@ func readEditorDefinitions() (map[string][]byte, error) {
var devfile map[string]interface{}
err = yaml.Unmarshal(editorContent, &devfile)
if err != nil {
return editorDefinitions, err
logger.Error(err, "Failed to unmarshal editor definition", "file", fileName)
continue
}
updateEditorDefinitionImageFromEnv(devfile)
updateEditorDefinitionImages(devfile)
editorContent, err = yaml.Marshal(devfile)
if err != nil {
return editorDefinitions, err
@ -83,7 +109,7 @@ func readEditorDefinitions() (map[string][]byte, error) {
return editorDefinitions, nil
}
func updateEditorDefinitionImageFromEnv(devfile map[string]interface{}) {
func updateEditorDefinitionImages(devfile map[string]interface{}) {
notAllowedCharsReg, _ := regexp.Compile("[^a-zA-Z0-9]+")
metadata := devfile["metadata"].(map[string]interface{})

View File

@ -10,7 +10,7 @@
// Red Hat, Inc. - initial API and implementation
//
package pluginregistry
package editorsdefinitions
import (
"os"
@ -61,6 +61,7 @@ func TestSyncEditorDefinitions(t *testing.T) {
editorDefinitions, err := readEditorDefinitions()
assert.NoError(t, err)
assert.NotEmpty(t, editorDefinitions)
assert.Len(t, editorDefinitions, 1)
done, err := syncEditorDefinitions(ctx, editorDefinitions)
assert.NoError(t, err)

View File

@ -0,0 +1,28 @@
//
// Copyright (c) 2019-2024 Red Hat, Inc.
// This program and the accompanying materials are made
// available under the terms of the Eclipse Public License 2.0
// which is available at https://www.eclipse.org/legal/epl-2.0/
//
// SPDX-License-Identifier: EPL-2.0
//
// Contributors:
// Red Hat, Inc. - initial API and implementation
//
package editorsdefinitions
import (
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
defaults "github.com/eclipse-che/che-operator/pkg/common/operator-defaults"
"github.com/eclipse-che/che-operator/pkg/common/test"
)
func init() {
test.EnableTestMode()
infrastructure.InitializeForTesting(infrastructure.OpenShiftv4)
defaults.InitializeForTesting("../../../config/manager/manager.yaml")
editorsDefinitionsDir = "./test-editors-definitions"
}

View File

@ -15,6 +15,7 @@ metadata:
name: che-code
attributes:
version: 1.2.3
publisher: test
components:
- name: component-a
container:

View File

@ -143,8 +143,10 @@ func getImagePullerCustomResourceName(ctx *chetypes.DeployContext) string {
}
func getDefaultImages(ctx *chetypes.DeployContext) string {
urls := collectRegistriesUrls(ctx)
allImages := fetchImagesFromRegistries(urls, ctx)
allImages := make(map[string]bool)
addImagesFromRegistries(ctx, allImages)
addImagesFromEditorsDefinitions(allImages)
// having them sorted, prevents from constant changing CR spec
sortedImages := sortImages(allImages)
@ -154,18 +156,6 @@ func getDefaultImages(ctx *chetypes.DeployContext) string {
func collectRegistriesUrls(ctx *chetypes.DeployContext) []string {
urls := make([]string, 0)
if ctx.CheCluster.Status.PluginRegistryURL != "" {
urls = append(
urls,
fmt.Sprintf(
"http://%s.%s.svc:8080/v3/%s",
constants.PluginRegistryName,
ctx.CheCluster.Namespace,
"external_images.txt",
),
)
}
if ctx.CheCluster.Status.DevfileRegistryURL != "" {
urls = append(
urls,
@ -181,9 +171,8 @@ func collectRegistriesUrls(ctx *chetypes.DeployContext) []string {
return urls
}
func fetchImagesFromRegistries(urls []string, ctx *chetypes.DeployContext) map[string]bool {
// return as map to make the list unique
allImages := make(map[string]bool)
func addImagesFromRegistries(ctx *chetypes.DeployContext, allImages map[string]bool) {
urls := collectRegistriesUrls(ctx)
for _, url := range urls {
images, err := fetchImagesFromUrl(url, ctx)
@ -195,8 +184,13 @@ func fetchImagesFromRegistries(urls []string, ctx *chetypes.DeployContext) map[s
}
}
}
}
return allImages
func addImagesFromEditorsDefinitions(allImages map[string]bool) {
envs := utils.GetEnvsByRegExp("RELATED_IMAGE_editor_definition_.*")
for _, env := range envs {
allImages[env.Value] = true
}
}
func sortImages(images map[string]bool) []string {

View File

@ -23,6 +23,4 @@ func init() {
infrastructure.InitializeForTesting(infrastructure.OpenShiftv4)
defaults.InitializeForTesting("../../../config/manager/manager.yaml")
editorsDefinitionsDir = "./test-editors-definitions"
}

View File

@ -16,9 +16,6 @@ import (
"fmt"
"strings"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"github.com/eclipse-che/che-operator/pkg/common/chetypes"
"github.com/eclipse-che/che-operator/pkg/common/constants"
"github.com/eclipse-che/che-operator/pkg/deploy/gateway"
@ -38,25 +35,16 @@ func NewPluginRegistryReconciler() *PluginRegistryReconciler {
func (p *PluginRegistryReconciler) Reconcile(ctx *chetypes.DeployContext) (reconcile.Result, bool, error) {
if ctx.CheCluster.Spec.Components.PluginRegistry.DisableInternalRegistry {
_, _ = deploy.DeleteNamespacedObject(ctx, constants.PluginRegistryName, &corev1.Service{})
_, _ = deploy.DeleteNamespacedObject(ctx, constants.PluginRegistryName, &corev1.ConfigMap{})
_, _ = deploy.DeleteNamespacedObject(ctx, editorsDefinitionsConfigMapName, &corev1.ConfigMap{})
_, _ = deploy.DeleteNamespacedObject(ctx, gateway.GatewayConfigMapNamePrefix+constants.PluginRegistryName, &corev1.ConfigMap{})
_, _ = deploy.DeleteNamespacedObject(ctx, constants.PluginRegistryName, &appsv1.Deployment{})
if ctx.CheCluster.Status.PluginRegistryURL != "" {
ctx.CheCluster.Status.PluginRegistryURL = ""
err := deploy.UpdateCheCRStatus(ctx, "PluginRegistryURL", "")
return reconcile.Result{}, err == nil, err
}
return reconcile.Result{}, true, nil
}
done, err := p.syncEditors(ctx)
if !done {
return reconcile.Result{}, false, err
}
done, err = p.syncService(ctx)
done, err := p.syncService(ctx)
if !done {
return reconcile.Result{}, false, err
}

View File

@ -16,7 +16,6 @@ import (
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
"github.com/eclipse-che/che-operator/pkg/common/test"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -37,7 +36,6 @@ func TestPluginRegistryReconcile(t *testing.T) {
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "plugin-registry", Namespace: "eclipse-che"}, &corev1.Service{}))
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "plugin-registry", Namespace: "eclipse-che"}, &corev1.ConfigMap{}))
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "editors-definitions", Namespace: "eclipse-che"}, &corev1.ConfigMap{}))
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "plugin-registry", Namespace: "eclipse-che"}, &appsv1.Deployment{}))
assert.NotEmpty(t, ctx.CheCluster.Status.PluginRegistryURL)
}

View File

@ -37,13 +37,11 @@ func NewPostgresReconciler() *PostgresReconciler {
func (p *PostgresReconciler) Reconcile(ctx *chetypes.DeployContext) (reconcile.Result, bool, error) {
// PostgreSQL component is not used anymore
_, _ = p.syncDeployment(ctx)
_, _ = p.syncPVC(ctx)
_, _ = p.syncCredentials(ctx)
_, _ = p.syncService(ctx)
// Backup server component is not used anymore
_, _ = p.syncBackupDeployment(ctx)
_, _ = deploy.DeleteNamespacedObject(ctx, postgresComponentName, &appsv1.Deployment{})
_, _ = deploy.DeleteNamespacedObject(ctx, backupServerComponentName, &appsv1.Deployment{})
_, _ = deploy.DeleteNamespacedObject(ctx, defaultPostgresVolumeClaimName, &corev1.PersistentVolumeClaim{})
_, _ = deploy.DeleteNamespacedObject(ctx, defaultPostgresCredentialsSecret, &corev1.Secret{})
_, _ = deploy.DeleteNamespacedObject(ctx, postgresComponentName, &corev1.Service{})
return reconcile.Result{}, true, nil
}
@ -51,23 +49,3 @@ func (p *PostgresReconciler) Reconcile(ctx *chetypes.DeployContext) (reconcile.R
func (p *PostgresReconciler) Finalize(ctx *chetypes.DeployContext) bool {
return true
}
func (p *PostgresReconciler) syncService(ctx *chetypes.DeployContext) (bool, error) {
return deploy.DeleteNamespacedObject(ctx, postgresComponentName, &corev1.Service{})
}
func (p *PostgresReconciler) syncPVC(ctx *chetypes.DeployContext) (bool, error) {
return deploy.DeleteNamespacedObject(ctx, defaultPostgresVolumeClaimName, &corev1.PersistentVolumeClaim{})
}
func (p *PostgresReconciler) syncDeployment(ctx *chetypes.DeployContext) (bool, error) {
return deploy.DeleteNamespacedObject(ctx, postgresComponentName, &appsv1.Deployment{})
}
func (p *PostgresReconciler) syncCredentials(ctx *chetypes.DeployContext) (bool, error) {
return deploy.DeleteNamespacedObject(ctx, defaultPostgresCredentialsSecret, &corev1.Secret{})
}
func (p *PostgresReconciler) syncBackupDeployment(ctx *chetypes.DeployContext) (bool, error) {
return deploy.DeleteNamespacedObject(ctx, backupServerComponentName, &appsv1.Deployment{})
}

View File

@ -20,14 +20,7 @@ import (
)
func SyncServiceAccountToCluster(deployContext *chetypes.DeployContext, name string) (bool, error) {
saSpec := getServiceAccountSpec(deployContext, name)
_, err := CreateIfNotExists(deployContext, saSpec)
return err == nil, err
}
func getServiceAccountSpec(deployContext *chetypes.DeployContext, name string) *corev1.ServiceAccount {
labels := GetLabels(defaults.GetCheFlavor())
serviceAccount := &corev1.ServiceAccount{
sa := &corev1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
Kind: "ServiceAccount",
APIVersion: "v1",
@ -35,9 +28,9 @@ func getServiceAccountSpec(deployContext *chetypes.DeployContext, name string) *
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: deployContext.CheCluster.Namespace,
Labels: labels,
Labels: GetLabels(defaults.GetCheFlavor()),
},
}
return serviceAccount
return CreateIgnoreIfExists(deployContext, sa)
}

View File

@ -0,0 +1,29 @@
//
// Copyright (c) 2019-2023 Red Hat, Inc.
// This program and the accompanying materials are made
// available under the terms of the Eclipse Public License 2.0
// which is available at https://www.eclipse.org/legal/epl-2.0/
//
// SPDX-License-Identifier: EPL-2.0
//
// Contributors:
// Red Hat, Inc. - initial API and implementation
//
package deploy
import (
"github.com/eclipse-che/che-operator/pkg/common/test"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime"
"testing"
)
func TestSyncServiceAccountToCluster(t *testing.T) {
ctx := test.GetDeployContext(nil, []runtime.Object{})
done, err := SyncServiceAccountToCluster(ctx, "test")
assert.NoError(t, err)
assert.True(t, done)
}

View File

@ -54,7 +54,7 @@ func SyncWithClient(cli client.Client, deployContext *chetypes.DeployContext, bl
}
key := types.NamespacedName{Name: blueprint.GetName(), Namespace: blueprint.GetNamespace()}
exists, err := GetWithClient(cli, key, actual.(client.Object))
exists, err := doGet(context.TODO(), cli, key, actual.(client.Object))
if err != nil {
return false, err
}
@ -62,17 +62,18 @@ func SyncWithClient(cli client.Client, deployContext *chetypes.DeployContext, bl
// set GroupVersionKind (it might be empty)
actual.GetObjectKind().SetGroupVersionKind(runtimeObject.GetObjectKind().GroupVersionKind())
if !exists {
return CreateWithClient(cli, deployContext, blueprint, false)
return doCreate(context.TODO(), cli, deployContext, blueprint, false)
}
return UpdateWithClient(cli, deployContext, actual.(client.Object), blueprint, diffOpts...)
return doUpdate(cli, deployContext, actual.(client.Object), blueprint, diffOpts...)
}
// Gets object by key.
// Get gets object.
// Returns true if object exists otherwise returns false.
// Returns error if object cannot be retrieved otherwise returns nil.
func Get(deployContext *chetypes.DeployContext, key client.ObjectKey, actual client.Object) (bool, error) {
cli := getClientForObject(key.Namespace, deployContext)
return GetWithClient(cli, key, actual)
return doGet(context.TODO(), cli, key, actual)
}
// Gets namespaced scope object by name
@ -80,7 +81,7 @@ func Get(deployContext *chetypes.DeployContext, key client.ObjectKey, actual cli
func GetNamespacedObject(deployContext *chetypes.DeployContext, name string, actual client.Object) (bool, error) {
client := deployContext.ClusterAPI.Client
key := types.NamespacedName{Name: name, Namespace: deployContext.CheCluster.Namespace}
return GetWithClient(client, key, actual)
return doGet(context.TODO(), client, key, actual)
}
// Gets cluster scope object by name
@ -88,34 +89,15 @@ func GetNamespacedObject(deployContext *chetypes.DeployContext, name string, act
func GetClusterObject(deployContext *chetypes.DeployContext, name string, actual client.Object) (bool, error) {
client := deployContext.ClusterAPI.NonCachingClient
key := types.NamespacedName{Name: name}
return GetWithClient(client, key, actual)
return doGet(context.TODO(), client, key, actual)
}
// Creates object.
// Return true if a new object is created, false if it has been already created or error occurred.
func CreateIfNotExists(deployContext *chetypes.DeployContext, blueprint client.Object) (isCreated bool, err error) {
// CreateIgnoreIfExists creates object.
// Return true if a new object is created or object already exists, otherwise returns false.
// Throws error if object cannot be created otherwise returns nil.
func CreateIgnoreIfExists(deployContext *chetypes.DeployContext, blueprint client.Object) (bool, error) {
cli := getClientForObject(blueprint.GetNamespace(), deployContext)
return CreateIfNotExistsWithClient(cli, deployContext, blueprint)
}
func CreateIfNotExistsWithClient(cli client.Client, deployContext *chetypes.DeployContext, blueprint client.Object) (isCreated bool, err error) {
key := types.NamespacedName{Name: blueprint.GetName(), Namespace: blueprint.GetNamespace()}
actual := blueprint.DeepCopyObject().(client.Object)
exists, err := GetWithClient(cli, key, actual)
if exists {
return false, nil
} else if err != nil {
return false, err
}
return CreateWithClient(cli, deployContext, blueprint, false)
}
// Creates object.
// Return true if a new object is created otherwise returns false.
func Create(deployContext *chetypes.DeployContext, blueprint client.Object) (bool, error) {
client := getClientForObject(blueprint.GetNamespace(), deployContext)
return CreateWithClient(client, deployContext, blueprint, false)
return doCreate(context.TODO(), cli, deployContext, blueprint, true)
}
// Deletes object.
@ -137,60 +119,6 @@ func DeleteClusterObject(deployContext *chetypes.DeployContext, name string, obj
return DeleteByKeyWithClient(client, key, objectMeta)
}
// Updates object.
// Returns true if object is up to date otherwiser return false
func UpdateWithClient(client client.Client, deployContext *chetypes.DeployContext, actual client.Object, blueprint client.Object, diffOpts ...cmp.Option) (bool, error) {
actualMeta, ok := actual.(metav1.Object)
if !ok {
return false, fmt.Errorf("object %T is not a metav1.Object. Cannot sync it", actualMeta)
}
diff := cmp.Diff(actual, blueprint, diffOpts...)
if len(diff) > 0 {
// don't print difference if there are no diffOpts mainly to avoid huge output
if len(diffOpts) != 0 {
fmt.Printf("Difference:\n%s", diff)
}
if isUpdateUsingDeleteCreate(actual.GetObjectKind().GroupVersionKind().Kind) {
done, err := doDeleteIgnoreIfNotFound(context.TODO(), client, actual)
if !done {
return false, err
}
return CreateWithClient(client, deployContext, blueprint, false)
} else {
err := setOwnerReferenceIfNeeded(deployContext, blueprint)
if err != nil {
return false, err
}
// to be able to update, we need to set the resource version of the object that we know of
blueprint.(metav1.Object).SetResourceVersion(actualMeta.GetResourceVersion())
err = client.Update(context.TODO(), blueprint)
syncLog.Info("Object updated", "namespace", actual.GetNamespace(), "kind", GetObjectType(actual), "name", actual.GetName())
return false, err
}
}
return true, nil
}
func CreateWithClient(client client.Client, deployContext *chetypes.DeployContext, blueprint client.Object, returnTrueIfAlreadyExists bool) (bool, error) {
err := setOwnerReferenceIfNeeded(deployContext, blueprint)
if err != nil {
return false, err
}
err = client.Create(context.TODO(), blueprint)
if err == nil {
syncLog.Info("Object created", "namespace", blueprint.GetNamespace(), "kind", GetObjectType(blueprint), "name", blueprint.GetName())
return true, nil
} else if errors.IsAlreadyExists(err) {
return returnTrueIfAlreadyExists, nil
} else {
return false, err
}
}
func DeleteByKeyWithClient(cli client.Client, key client.ObjectKey, objectMeta client.Object) (bool, error) {
runtimeObject, ok := objectMeta.(runtime.Object)
if !ok {
@ -198,7 +126,7 @@ func DeleteByKeyWithClient(cli client.Client, key client.ObjectKey, objectMeta c
}
actual := runtimeObject.DeepCopyObject().(client.Object)
exists, err := GetWithClient(cli, key, actual)
exists, err := doGet(context.TODO(), cli, key, actual)
if !exists {
return true, nil
} else if err != nil {
@ -208,17 +136,6 @@ func DeleteByKeyWithClient(cli client.Client, key client.ObjectKey, objectMeta c
return doDeleteIgnoreIfNotFound(context.TODO(), cli, actual)
}
func GetWithClient(client client.Client, key client.ObjectKey, object client.Object) (bool, error) {
err := client.Get(context.TODO(), key, object)
if err == nil {
return true, nil
} else if errors.IsNotFound(err) {
return false, nil
} else {
return false, err
}
}
func isUpdateUsingDeleteCreate(kind string) bool {
return "Service" == kind || "Ingress" == kind || "Route" == kind || "Job" == kind || "Secret" == kind
}
@ -255,7 +172,13 @@ func GetObjectType(obj interface{}) string {
// DeleteIgnoreIfNotFound deletes object.
// Returns nil if object deleted or not found otherwise returns error.
func DeleteIgnoreIfNotFound(context context.Context, cli client.Client, key client.ObjectKey, blueprint client.Object) error {
// Return error if object cannot be deleted otherwise returns nil.
func DeleteIgnoreIfNotFound(
context context.Context,
cli client.Client,
key client.ObjectKey,
blueprint client.Object,
) error {
runtimeObj, ok := blueprint.(runtime.Object)
if !ok {
return fmt.Errorf("object %T is not a runtime.Object. Cannot sync it", runtimeObj)
@ -273,9 +196,14 @@ func DeleteIgnoreIfNotFound(context context.Context, cli client.Client, key clie
}
// doCreate creates object.
// Returns true if object created otherwise returns false.
// Throws error if object cannot be created or already exists otherwise returns nil.
func doCreate(context context.Context, client client.Client, deployContext *chetypes.DeployContext, blueprint client.Object) (bool, error) {
// Return error if object cannot be created otherwise returns nil.
func doCreate(
context context.Context,
client client.Client,
deployContext *chetypes.DeployContext,
blueprint client.Object,
returnTrueIfAlreadyExists bool,
) (bool, error) {
err := setOwnerReferenceIfNeeded(deployContext, blueprint)
if err != nil {
return false, err
@ -285,6 +213,8 @@ func doCreate(context context.Context, client client.Client, deployContext *chet
if err == nil {
syncLog.Info("Object created", "namespace", blueprint.GetNamespace(), "kind", GetObjectType(blueprint), "name", blueprint.GetName())
return true, nil
} else if errors.IsAlreadyExists(err) {
return returnTrueIfAlreadyExists, nil
} else {
return false, err
}
@ -293,7 +223,11 @@ func doCreate(context context.Context, client client.Client, deployContext *chet
// doDeleteIgnoreIfNotFound deletes object.
// Returns true if object deleted or not found otherwise returns false.
// Returns error if object cannot be deleted otherwise returns nil.
func doDeleteIgnoreIfNotFound(context context.Context, cli client.Client, actual client.Object) (bool, error) {
func doDeleteIgnoreIfNotFound(
context context.Context,
cli client.Client,
actual client.Object,
) (bool, error) {
err := cli.Delete(context, actual)
if err == nil {
if errors.IsNotFound(err) {
@ -310,7 +244,12 @@ func doDeleteIgnoreIfNotFound(context context.Context, cli client.Client, actual
// doGet gets object.
// Returns true if object exists otherwise returns false.
// Returns error if object cannot be retrieved otherwise returns nil.
func doGet(context context.Context, cli client.Client, key client.ObjectKey, object client.Object) (bool, error) {
func doGet(
context context.Context,
cli client.Client,
key client.ObjectKey,
object client.Object,
) (bool, error) {
err := cli.Get(context, key, object)
if err == nil {
return true, nil
@ -320,3 +259,49 @@ func doGet(context context.Context, cli client.Client, key client.ObjectKey, obj
return false, err
}
}
// doUpdate updates object.
// Returns true if object is up-to-date otherwise return false
func doUpdate(
cli client.Client,
deployContext *chetypes.DeployContext,
actual client.Object,
blueprint client.Object,
diffOpts ...cmp.Option,
) (bool, error) {
actualMeta, ok := actual.(metav1.Object)
if !ok {
return false, fmt.Errorf("object %T is not a metav1.Object. Cannot sync it", actualMeta)
}
diff := cmp.Diff(actual, blueprint, diffOpts...)
if len(diff) > 0 {
// don't print difference if there are no diffOpts mainly to avoid huge output
if len(diffOpts) != 0 {
fmt.Printf("Difference:\n%s", diff)
}
if isUpdateUsingDeleteCreate(actual.GetObjectKind().GroupVersionKind().Kind) {
done, err := doDeleteIgnoreIfNotFound(context.TODO(), cli, actual)
if !done {
return false, err
}
return doCreate(context.TODO(), cli, deployContext, blueprint, false)
} else {
err := setOwnerReferenceIfNeeded(deployContext, blueprint)
if err != nil {
return false, err
}
// to be able to update, we need to set the resource version of the object that we know of
blueprint.(metav1.Object).SetResourceVersion(actualMeta.GetResourceVersion())
err = cli.Update(context.TODO(), blueprint)
if err == nil {
syncLog.Info("Object updated", "namespace", actual.GetNamespace(), "kind", GetObjectType(actual), "name", actual.GetName())
}
return false, err
}
}
return true, nil
}

View File

@ -15,6 +15,8 @@ package deploy
import (
"context"
"github.com/stretchr/testify/assert"
chev2 "github.com/eclipse-che/che-operator/api/v2"
"github.com/eclipse-che/che-operator/pkg/common/chetypes"
"github.com/google/go-cmp/cmp"
@ -74,68 +76,28 @@ func TestGet(t *testing.T) {
}
}
func TestCreate(t *testing.T) {
func TestCreateIgnoreIfExistsShouldReturnTrueIfObjectCreated(t *testing.T) {
cli, deployContext := initDeployContext()
done, err := Create(deployContext, testObj.DeepCopy())
if err != nil {
t.Fatalf("Failed to create object: %v", err)
}
if !done {
t.Fatalf("Object has not been created")
}
done, err := CreateIgnoreIfExists(deployContext, testObj.DeepCopy())
assert.NoError(t, err)
assert.True(t, done)
actual := &corev1.Secret{}
err = cli.Get(context.TODO(), testKey, actual)
if err != nil && !errors.IsNotFound(err) {
t.Fatalf("Failed to get object: %v", err)
}
if actual == nil {
t.Fatalf("Object not found")
}
assert.NoError(t, err)
assert.NotNil(t, actual)
}
func TestCreateIfNotExistsShouldReturnTrueIfObjectCreated(t *testing.T) {
cli, deployContext := initDeployContext()
done, err := CreateIfNotExists(deployContext, testObj.DeepCopy())
if err != nil {
t.Fatalf("Failed to create object: %v", err)
}
if !done {
t.Fatalf("Object has not been created")
}
actual := &corev1.Secret{}
err = cli.Get(context.TODO(), testKey, actual)
if err != nil && !errors.IsNotFound(err) {
t.Fatalf("Failed to get object: %v", err)
}
if actual == nil {
t.Fatalf("Object not found")
}
}
func TestCreateIfNotExistsShouldReturnFalseIfObjectExist(t *testing.T) {
func TestCreateIgnoreIfExistsShouldReturnTrueIfObjectExist(t *testing.T) {
cli, deployContext := initDeployContext()
err := cli.Create(context.TODO(), testObj.DeepCopy())
if err != nil {
t.Fatalf("Failed to create object: %v", err)
}
assert.NoError(t, err)
isCreated, err := CreateIfNotExists(deployContext, testObj.DeepCopy())
if err != nil {
t.Fatalf("Failed to create object: %v", err)
}
if isCreated {
t.Fatalf("Object has been created")
}
done, err := CreateIgnoreIfExists(deployContext, testObj.DeepCopy())
assert.NoError(t, err)
assert.True(t, done)
}
func TestUpdate(t *testing.T) {
@ -152,7 +114,7 @@ func TestUpdate(t *testing.T) {
t.Fatalf("Failed to get object: %v", err)
}
_, err = UpdateWithClient(cli, deployContext, actual, testObjLabeled.DeepCopy(), cmp.Options{})
_, err = doUpdate(cli, deployContext, actual, testObjLabeled.DeepCopy(), cmp.Options{})
if err != nil {
t.Fatalf("Failed to update object: %v", err)
}

View File

@ -94,8 +94,7 @@ func (c *CertificatesReconciler) syncTrustStoreConfigMapToCluster(ctx *chetypes.
if !exists {
// We have to create an empty config map with the specific labels
done, err := deploy.Create(ctx, configMapSpec)
return done, err
return deploy.CreateIgnoreIfExists(ctx, configMapSpec)
}
if actual.ObjectMeta.Labels[injector] != "true" ||