Compare commits

..

3 Commits
main ... 7.80.0

Author SHA1 Message Date
Mykhailo Kuznietsov 73e0dfe39a chore: Release version 7.80.0
Signed-off-by: Mykhailo Kuznietsov <mkuznets@redhat.com>
2024-01-10 15:33:36 +00:00
Mykhailo Kuznietsov 5dcb06f0cd chore: Set 7.80.0 release image tags
Signed-off-by: Mykhailo Kuznietsov <mkuznets@redhat.com>
2024-01-10 15:26:33 +00:00
Mykhailo Kuznietsov 98d42025e1 chore: Bump to 7.80.1-SNAPSHOT in 7.80.x
Signed-off-by: Mykhailo Kuznietsov <mkuznets@redhat.com>
2024-01-10 15:26:32 +00:00
455 changed files with 41143 additions and 1412 deletions

View File

@ -11,19 +11,30 @@
# Red Hat, Inc. - initial API and implementation
# Dockerfile to bootstrap build and test in openshift-ci
FROM registry.ci.openshift.org/openshift/release:golang-1.18
FROM registry.access.redhat.com/ubi9/nodejs-18:1
# hadolint ignore=DL3002
USER 0
SHELL ["/bin/bash", "-c"]
# Install yq, kubectl, chectl cli used by olm/olm.sh script.
# hadolint ignore=DL3041
# Install yq, kubectl, chectl cli.
RUN yum install --assumeyes -d1 psmisc python3-pip httpd-tools nodejs && \
pip3 install --upgrade setuptools && \
pip3 install yq && \
RUN dnf install -y -q --allowerasing --nobest nodejs-devel nodejs-libs psmisc python3-pip jq golang httpd-tools \
# already installed or installed as deps:
openssl openssl-devel ca-certificates make cmake cpp gcc gcc-c++ zlib zlib-devel brotli brotli-devel python3 nodejs-packaging && \
dnf update -y && dnf clean all && \
npm install -g yarn@1.22 npm@9 && \
echo -n "node version: "; node -v; \
echo -n "npm version: "; npm -v; \
echo -n "yarn version: "; yarn -v; \
go version; \
pip3 install --upgrade pip setuptools yq && \
# Install kubectl, chectl cli used by olm/olm.sh script.
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \
chmod +x ./kubectl && \
mv ./kubectl /usr/local/bin && \
bash <(curl -sL https://che-incubator.github.io/chectl/install.sh) --channel=next
bash <(curl -sL https://www.eclipse.org/che/chectl/) --channel=next && \
curl https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest-4.12/openshift-client-linux.tar.gz | tar xvzf - -C /usr/local/bin/ oc && \
chmod ug+x /usr/local/bin/oc
SHELL ["/bin/bash", "-c"]

View File

@ -1,6 +1,6 @@
#!/bin/bash
#
# Copyright (c) 2023-2024 Red Hat, Inc.
# Copyright (c) 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/
@ -36,47 +36,39 @@ export CUSTOM_CONFIG_MAP_NAME=${CUSTOM_CONFIG_MAP_NAME:-"custom-ca-certificates"
export GIT_SSL_CONFIG_MAP_NAME=${GIT_SSL_CONFIG_MAP_NAME:-"che-self-signed-cert"}
provisionOpenShiftOAuthUser() {
echo "------- [INFO] Start provisioning Openshift OAuth user -------"
echo -e "[INFO] Provisioning Openshift OAuth user"
htpasswd -c -B -b users.htpasswd ${OCP_ADMIN_USER_NAME} ${OCP_LOGIN_PASSWORD}
htpasswd -b users.htpasswd ${OCP_NON_ADMIN_USER_NAME} ${OCP_LOGIN_PASSWORD}
oc create secret generic htpass-secret --from-file=htpasswd="users.htpasswd" -n openshift-config
oc apply -f ".ci/openshift-ci/htpasswdProvider.yaml"
oc adm policy add-cluster-role-to-user cluster-admin ${OCP_ADMIN_USER_NAME}
echo "------- [INFO] Waiting for htpasswd auth to be working up to 5 minutes -------"
echo -e "[INFO] Waiting for htpasswd auth to be working up to 5 minutes"
CURRENT_TIME=$(date +%s)
ENDTIME=$((CURRENT_TIME + 300))
while [ "$(date +%s)" -lt $ENDTIME ]; do
if oc login -u=${OCP_ADMIN_USER_NAME} -p=${OCP_LOGIN_PASSWORD} --insecure-skip-tls-verify=false; then
echo "======= [INFO] OpenShift OAuth htpasswd is configured. =======
======= [INFO] Login to OCP cluster with admin user credentials is success.======="
return 0
break
fi
sleep 5
sleep 10
done
echo "####### [ERROR] Error occurred while waiting OpenShift OAuth htpasswd setup. Try to rerun test. #######"
exit 1
}
configureGitSelfSignedCertificate() {
echo "------- [INFO] Configure self-signed certificate for Git provider -------"
echo "[INFO] Configure self-signed certificate for Git provider"
oc adm new-project ${CHE_NAMESPACE}
oc project ${CHE_NAMESPACE}
echo "------- [INFO] Create ConfigMap with the required TLS certificate -------"
echo -e "[INFO] Create ConfigMap with the required TLS certificate"
oc create configmap ${CUSTOM_CONFIG_MAP_NAME} --from-file=.ci/openshift-ci/ca.crt
oc label configmap ${CUSTOM_CONFIG_MAP_NAME} app.kubernetes.io/part-of=che.eclipse.org app.kubernetes.io/component=ca-bundle
echo "------- [INFO] Create ConfigMap to support Git repositories with self-signed certificates -------"
oc create configmap ${GIT_SSL_CONFIG_MAP_NAME} --from-file=.ci/openshift-ci/ca.crt --from-literal=githost=${GIT_PROVIDER_URL}
echo "[INFO] Create ConfigMap to support Git repositories with self-signed certificates"
oc create configmap ${GIT_SSL_CONFIG_MAP_NAME} --from-file=.ci/openshift-ci/ca.crt --from-literal=githost=$GIT_PROVIDER_URL
oc label configmap ${GIT_SSL_CONFIG_MAP_NAME} app.kubernetes.io/part-of=che.eclipse.org
echo "======= [INFO] ConfigMaps are configured ======="
}
createCustomResourcesFile() {
echo "------- [INFO] Create custom resourses file -------"
cat > custom-resources.yaml <<-END
apiVersion: org.eclipse.che/v2
spec:
@ -84,53 +76,26 @@ spec:
maxNumberOfRunningWorkspacesPerUser: 10000
END
echo "======= [INFO] Generated custom resources file ======="
echo "Generated custom resources file"
cat custom-resources.yaml
}
patchCustomResourcesFile() {
echo "------- [INFO] Edit the custom resources file to add 'gitTrustedCertsConfigMapName' -------"
yq -y '.spec.devEnvironments.trustedCerts += {"gitTrustedCertsConfigMapName": "'${GIT_SSL_CONFIG_MAP_NAME}'"}' custom-resources.yaml -i
echo "======= [INFO] Patched custom resources file ======="
cat custom-resources.yaml
}
deployChe() {
echo "------- [INFO] Start installing Eclipse Che -------"
chectl server:deploy --cheimage=$CHE_SERVER_IMAGE \
--che-operator-cr-patch-yaml=custom-resources.yaml \
--platform=openshift \
--telemetry=off \
--batch
waitFinishDeploymentCheServer
echo "======= [INFO] Eclipse Che is successfully installed ======="
}
waitFinishDeploymentCheServer() {
CURRENT_TIME=$(date +%s)
ENDTIME=$((CURRENT_TIME + 60))
while [ "$(date +%s)" -lt $ENDTIME ]; do
podCheServerName=$(oc get pod -n ${CHE_NAMESPACE} -l component=che | grep "che" | awk '{ print $1 }')
echo "Pod Che_Server: $podCheServerName"
count=$(echo "$podCheServerName" | wc -l)
if [ $count -eq 1 ]; then
echo "------- [INFO] Only one Che Server pod is left. -------"
return 0
fi
echo "------- [INFO] Waiting until only one Che Server pod remains. -------"
sleep 5
done
echo "####### [ERROR] Error occurred while waiting for only one Che Server pod. #######"
exit 1
}
# this command starts port forwarding between the local machine and the che-host service in the OpenShift cluster.
forwardPortToService() {
echo "------- [INFO] Start forwarding between the local machine and the che-host service -------"
oc port-forward service/che-host ${CHE_FORWARDED_PORT}:8080 -n ${CHE_NAMESPACE} &
sleep 3s
}
@ -155,28 +120,28 @@ requestFactoryResolverGitRepoUrl() {
# check that factory resolver returns correct value without any PAT/OAuth setup
testFactoryResolverNoPatOAuth() {
echo "------- [INFO] Check factory resolver for public repository with NO PAT/OAuth setup -------"
echo "[INFO] Check factory resolver for public repository with NO PAT/OAuth setup"
testFactoryResolverResponse $1 200
echo "------- [INFO] Check factory resolver for private repository with NO PAT/OAuth setup -------"
echo "[INFO] Check factory resolver for private repository with NO PAT/OAuth setup"
testFactoryResolverResponse $2 500
}
# check that raw devfile url factory resolver returns correct value without any PAT/OAuth setup
testFactoryResolverNoPatOAuthRaw() {
echo "------- [INFO] Check factory resolver for public repository with NO PAT/OAuth setup -------"
echo "[INFO] Check factory resolver for public repository with NO PAT/OAuth setup"
testFactoryResolverResponse $1 200
echo "------- [INFO] Check factory resolver for private repository with NO PAT/OAuth setup -------"
echo "[INFO] Check factory resolver for private repository with NO PAT/OAuth setup"
testFactoryResolverResponse $2 400
}
# check that factory resolver returns correct value with PAT/OAuth setup
testFactoryResolverWithPatOAuth() {
echo "------- [INFO] Check factory resolver for public repository with PAT/OAuth setup -------"
echo "[INFO] Check factory resolver for public repository with PAT/OAuth setup"
testFactoryResolverResponse $1 200
echo "------- [INFO] Check factory resolver for private repository with PAT/OAuth setup -------"
echo "[INFO] Check factory resolver for private repository with PAT/OAuth setup"
testFactoryResolverResponse $2 200
}
@ -184,11 +149,11 @@ testFactoryResolverResponse() {
URL=$1
RESPONSE_CODE=$2
echo "[INFO] Check factory resolver"
if [ "$(requestFactoryResolverGitRepoUrl ${URL} | grep "HTTP/1.1 ${RESPONSE_CODE}")" ]; then
echo "======= [INFO] Factory resolver returned 'HTTP/1.1 ${RESPONSE_CODE}' status code. Expected client side response. ======="
echo "[INFO] Factory resolver returned 'HTTP/1.1 ${RESPONSE_CODE}' status code. Expected client side response."
else
echo "####### [ERROR] Factory resolver returned wrong status code. Expected: HTTP/1.1 ${RESPONSE_CODE}. #######
####### Cause possible: PR code regress or service is changed. Need to investigate it. #######"
echo "[ERROR] Factory resolver returned wrong status code. Expected: HTTP/1.1 ${RESPONSE_CODE}."
exit 1
fi
}
@ -206,19 +171,12 @@ requestProvisionNamespace() {
initUserNamespace() {
OCP_USER_NAME=$1
echo "------- [INFO] Initialize user namespace -------"
echo "[INFO] Initialize user namespace"
oc login -u=${OCP_USER_NAME} -p=${OCP_LOGIN_PASSWORD} --insecure-skip-tls-verify=false
request_result=$(requestProvisionNamespace) || true
if [ -z "$request_result" ]; then
echo "####### [ERROR] Cause possible: lost connection to pod, this is an infrastructure problem. Try to rerun the test. #######"
exit 1
elif [ "$(echo "$request_result" | grep "HTTP/1.1 200")" ]; then
echo "======= [INFO] Request provision user namespace returned 'HTTP/1.1 200' status code. User namespace is created. ======="
if [ "$(requestProvisionNamespace | grep "HTTP/1.1 200")" ]; then
echo "[INFO] Request provision user namespace returned 'HTTP/1.1 200' status code."
else
echo "####### [ERROR] Request provision user namespace returned wrong status code. Expected: HTTP/1.1 200. #######
####### User namespace creation failed. Cause possible: PR code regression or service is changed. Need to investigate. #######"
echo "[ERROR] Request provision user namespace returned wrong status code. Expected: HTTP/1.1 200"
exit 1
fi
}
@ -228,7 +186,7 @@ setupPersonalAccessToken() {
GIT_PROVIDER_URL=$2
GIT_PROVIDER_PAT=$3
echo "------- [INFO] Setup Personal Access Token Secret -------"
echo "[INFO] Setup Personal Access Token Secret"
oc project ${USER_CHE_NAMESPACE}
CHE_USER_ID=$(oc get secret user-profile -o jsonpath='{.data.id}' | base64 -d)
ENCODED_PAT=$(echo -n ${GIT_PROVIDER_PAT} | base64)
@ -247,14 +205,13 @@ setupPersonalAccessToken() {
cat pat-secret.yaml
oc apply -f pat-secret.yaml -n ${USER_CHE_NAMESPACE}
echo "======= [INFO] Personal Access Token is created. ======="
}
setupSSHKeyPairs() {
GIT_PRIVATE_KEY=$1
GIT_PUBLIC_KEY=$2
echo "------- [INFO] Setup SSH Key Pairs Secret "-------
echo "[INFO] Setup SSH Key Pairs Secret"
oc project ${USER_CHE_NAMESPACE}
ENCODED_GIT_PRIVATE_KEY=$(echo "${GIT_PRIVATE_KEY}" | base64 -w 0)
ENCODED_GIT_PUBLIC_KEY=$(echo "${GIT_PUBLIC_KEY}" | base64 -w 0)
@ -267,7 +224,6 @@ setupSSHKeyPairs() {
cat ssh-secret.yaml
oc apply -f ssh-secret.yaml -n ${USER_CHE_NAMESPACE}
echo "======= [INFO] SSH Secret is created. ======="
}
# Only for GitLab server administrator users
@ -276,7 +232,7 @@ createOAuthApplicationGitLabServer() {
CHE_URL=https://$(oc get route -n ${CHE_NAMESPACE} che -o jsonpath='{.spec.host}')
echo "------- [INFO] Create OAuth Application -------"
echo "[INFO] Create OAuth Application"
response=$(curl -k -X POST \
${GIT_PROVIDER_URL}/api/v4/applications \
-H "PRIVATE-TOKEN: ${ADMIN_ACCESS_TOKEN}" \
@ -284,11 +240,15 @@ createOAuthApplicationGitLabServer() {
-d "redirect_uri=${CHE_URL}/api/oauth/callback" \
-d "scopes=api write_repository openid")
echo "------- [INFO] Response of the created OAuth Application -------"
echo "[INFO] Response of the created OAuth Application"
OAUTH_ID=$(echo "$response" | jq -r '.id')
APPLICATION_ID=$(echo "$response" | jq -r '.application_id')
APPLICATION_SECRET=$(echo "$response" | jq -r '.secret')
echo "[INFO] OAuth ID: ${OAUTH_ID}"
echo "[INFO] Application ID: ${APPLICATION_ID}"
echo "[INFO] Application Secret: ${APPLICATION_SECRET}"
}
# Only for GitLab server administrator users
@ -296,12 +256,10 @@ deleteOAuthApplicationGitLabServer() {
OAUTH_ID=$1
ADMIN_ACCESS_TOKEN=$2
echo "------- [INFO] Delete OAuth Application -------"
echo "[INFO] Delete OAuth Application"
curl -i -k -X DELETE \
${GIT_PROVIDER_URL}/api/v4/applications/${OAUTH_ID} \
-H "PRIVATE-TOKEN: ${ADMIN_ACCESS_TOKEN}"
echo "======= [INFO] OAuth Application is deleted ======="
}
# Only for GitLab server
@ -309,18 +267,18 @@ revokeAuthorizedOAuthApplication() {
APPLICATION_ID=$1
APPLICATION_SECRET=$2
echo "------- [INFO] Revoke authorized OAuth application -------"
echo "[INFO] Revoke authorized OAuth application"
oc project ${USER_CHE_NAMESPACE}
OAUTH_TOKEN_NAME=$(oc get secret | grep 'personal-access-token'| awk 'NR==1 { print $1 }')
echo "[INFO] OAuth token name: "$OAUTH_TOKEN_NAME
OAUTH_TOKEN=$(oc get secret $OAUTH_TOKEN_NAME -o jsonpath='{.data.token}' | base64 -d)
echo "[INFO] Oauth token: "$OAUTH_TOKEN
curl -i -k -X POST \
${GIT_PROVIDER_URL}/oauth/revoke \
-d "client_id=${APPLICATION_ID}" \
-d "client_secret=${APPLICATION_SECRET}" \
-d "token=${OAUTH_TOKEN}"
echo "======= [INFO] Authorized OAuth application is revoked ======="
}
# Only for GitLab server
@ -328,7 +286,7 @@ setupOAuthSecret() {
APPLICATION_ID=$1
APPLICATION_SECRET=$2
echo "------- [INFO] Setup OAuth Secret -------"
echo "[INFO] Setup OAuth Secret"
oc login -u=${OCP_ADMIN_USER_NAME} -p=${OCP_LOGIN_PASSWORD} --insecure-skip-tls-verify=false
oc project ${CHE_NAMESPACE}
SERVER_POD=$(oc get pod -l component=che | grep "che" | awk 'NR==1 { print $1 }')
@ -345,7 +303,7 @@ setupOAuthSecret() {
cat oauth-secret.yaml
oc apply -f oauth-secret.yaml -n ${CHE_NAMESPACE}
echo "------- [INFO] Wait updating deployment after create OAuth secret -------"
echo "[INFO] Wait updating deployment after create OAuth secret"
oc wait --for=delete pod/${SERVER_POD} --timeout=120s
}
@ -358,7 +316,6 @@ runTestWorkspaceWithGitRepoUrl() {
oc project ${OCP_USER_NAMESPACE}
cat .ci/openshift-ci/devworkspace-test.yaml > devworkspace-test.yaml
echo "------- [INFO] Preparing 'devworkspace-test.yaml' and run test workspace -------"
# patch the devworkspace-test.yaml file
sed -i "s#ws-name#${WS_NAME}#g" devworkspace-test.yaml
sed -i "s#project-name#${PROJECT_NAME}#g" devworkspace-test.yaml
@ -368,7 +325,7 @@ runTestWorkspaceWithGitRepoUrl() {
oc apply -f devworkspace-test.yaml -n ${OCP_USER_NAMESPACE}
oc wait -n ${OCP_USER_NAMESPACE} --for=condition=Ready dw ${WS_NAME} --timeout=360s
echo "======= [INFO] Test workspace is run ======="
echo "[INFO] Test workspace is run"
}
testProjectIsCloned() {
@ -377,9 +334,9 @@ testProjectIsCloned() {
WORKSPACE_POD_NAME=$(oc get pods -n ${OCP_USER_NAMESPACE} | grep workspace | awk '{print $1}')
if oc exec -it -n ${OCP_USER_NAMESPACE} ${WORKSPACE_POD_NAME} -- test -f /projects/${PROJECT_NAME}/${YAML_FILE_NAME}; then
echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} exists. ======="
echo "[INFO] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} exists."
else
echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} is absent. ======="
echo "[INFO] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} is absent."
return 1
fi
}
@ -389,7 +346,7 @@ testGitCredentialsData() {
GIT_PROVIDER_PAT=$2
GIT_PROVIDER_URL=$3
echo "------- [INFO] Check the 'git credentials' is in a workspace -------"
echo "[INFO] Check the 'git credentials' is in a workspace"
hostName="${GIT_PROVIDER_URL#https://}"
if [ "${GIT_PROVIDER_TYPE}" == "azure-devops" ]; then
@ -401,10 +358,9 @@ testGitCredentialsData() {
gitCredentials="https://${userName}:${GIT_PROVIDER_PAT}@${hostName}"
WORKSPACE_POD_NAME=$(oc get pods -n ${OCP_USER_NAMESPACE} | grep workspace | awk '{print $1}')
if oc exec -it -n ${OCP_USER_NAMESPACE} ${WORKSPACE_POD_NAME} -- cat /.git-credentials/credentials | grep -q ${gitCredentials}; then
echo "======= [INFO] Git credentials file '/.git-credentials/credentials' exists and has the expected content. ======="
echo "[INFO] Git credentials file '/.git-credentials/credentials' exists and has the expected content."
else
echo "####### [ERROR] Git credentials file '/.git-credentials/credentials' does not exist or has incorrect content. ######
###### Cause possible: PR code regress or service is changed. Need to investigate it. #######"
echo "[ERROR] Git credentials file '/.git-credentials/credentials' does not exist or has incorrect content."
exit 1
fi
}
@ -412,7 +368,7 @@ testGitCredentialsData() {
deleteTestWorkspace() {
WS_NAME=$1
OCP_USER_NAMESPACE=$2
echo "------- [INFO] Delete test workspace -------"
oc delete dw ${WS_NAME} -n ${OCP_USER_NAMESPACE}
}
@ -430,7 +386,7 @@ startOAuthFactoryTest() {
sed -i "s#GIT_PROVIDER_USER_NAME#${GIT_PROVIDER_LOGIN}#g" oauth-factory-test.yaml
sed -i "s#GIT_PROVIDER_USER_PASSWORD#${GIT_PROVIDER_PASSWORD}#g" oauth-factory-test.yaml
echo "------- [INFO] Applying the following patched OAuth Factory Test Pod: -------"
echo "[INFO] Applying the following patched OAuth Factory Test Pod:"
cat oauth-factory-test.yaml
echo "[INFO] --------------------------------------------------"
oc apply -f oauth-factory-test.yaml
@ -441,7 +397,7 @@ startOAuthFactoryTest() {
PHASE=$(oc get pod -n ${CHE_NAMESPACE} ${TEST_POD_NAME} \
--template='{{ .status.phase }}')
if [[ ${PHASE} == "Running" ]]; then
echo "======= [INFO] Factory test started successfully. ======="
echo "[INFO] Smoke test started successfully."
return
fi
@ -449,59 +405,22 @@ startOAuthFactoryTest() {
n=$(( n+1 ))
done
echo "####### [ERROR] Failed to start Factory test. #######
###### Cause possible: an infrastructure problem, pod could not start, try to rerun the test. #######"
exit 1
}
startSmokeTest() {
CHE_URL=https://$(oc get route -n ${CHE_NAMESPACE} che -o jsonpath='{.spec.host}')
# patch che-smoke-test.yaml
cat .ci/openshift-ci/pod-che-smoke-test.yaml > che-smoke-test.yaml
sed -i "s#CHE_URL#${CHE_URL}#g" che-smoke-test.yaml
sed -i "s#CHE-NAMESPACE#${CHE_NAMESPACE}#g" che-smoke-test.yaml
sed -i "s#OCP_USER_NAME#${OCP_NON_ADMIN_USER_NAME}#g" che-smoke-test.yaml
sed -i "s#OCP_USER_PASSWORD#${OCP_LOGIN_PASSWORD}#g" che-smoke-test.yaml
echo "------- [INFO] Applying the following patched Smoke Test Pod: -------"
cat che-smoke-test.yaml
echo "[INFO] --------------------------------------------------"
oc apply -f che-smoke-test.yaml
# wait for the pod to start
n=0
while [ $n -le 120 ]
do
PHASE=$(oc get pod -n ${CHE_NAMESPACE} ${TEST_POD_NAME} \
--template='{{ .status.phase }}')
if [[ ${PHASE} == "Running" ]]; then
echo "======= [INFO] Smoke test started successfully. ======="
return
fi
sleep 5
n=$(( n+1 ))
done
echo "####### [ERROR] Failed to start Smoke test. #######
####### Cause possible: an infrastructure problem, pod could not start, try to rerun the test. #######"
echo "[ERROR] Failed to start smoke test."
exit 1
}
# Catch the finish of the job and write logs in artifacts.
catchFinish() {
echo "[INFO] Terminate the process after finish the test script."
local RESULT=$?
killProcessByPort
if [ "$RESULT" != "0" ]; then
set +e
collectEclipseCheLogs
set -e
fi
echo "------- [INFO] Terminate the process after finish the test script. -------"
set +e
killProcessByPort
set -e
[[ "${RESULT}" != "0" ]] && echo "####### [ERROR] Job failed. #######" || echo "####### [INFO] Job completed successfully. #######"
[[ "${RESULT}" != "0" ]] && echo "[ERROR] Job failed." || echo "[INFO] Job completed successfully."
exit $RESULT
}
@ -515,35 +434,31 @@ collectEclipseCheLogs() {
}
collectLogs() {
echo "------- [INFO] Waiting until test pod finished. -------"
oc logs -n ${CHE_NAMESPACE} ${TEST_POD_NAME} -c test -f
echo "[INFO] Waiting until oauth test pod finished"
oc logs -n ${CHE_NAMESPACE} ${TEST_POD_NAME} -c oauth-test -f
sleep 3
# Download artifacts
set +e
echo -------" [INFO] Collect all Eclipse Che logs and cluster CR. -------"
echo "[INFO] Collect all Eclipse Che logs and cluster CR."
collectEclipseCheLogs
echo "------- [INFO] Downloading test report. -------"
echo "[INFO] Downloading test report."
mkdir -p ${ARTIFACTS_DIR}/e2e
oc rsync -n ${CHE_NAMESPACE} ${TEST_POD_NAME}:/tmp/e2e/report/ ${ARTIFACTS_DIR}/e2e -c download-reports
oc exec -n ${CHE_NAMESPACE} ${TEST_POD_NAME} -c download-reports -- touch /tmp/done
# Revoke and delete the OAuth application
if [[ ${TEST_POD_NAME} == "oauth-factory-test" ]]; then
revokeAuthorizedOAuthApplication ${APPLICATION_ID} ${APPLICATION_SECRET}
deleteOAuthApplicationGitLabServer ${OAUTH_ID} ${ADMIN_ACCESS_TOKEN}
fi
revokeAuthorizedOAuthApplication ${APPLICATION_ID} ${APPLICATION_SECRET}
deleteOAuthApplicationGitLabServer ${OAUTH_ID} ${ADMIN_ACCESS_TOKEN}
set -e
EXIT_CODE=$(oc logs -n ${CHE_NAMESPACE} ${TEST_POD_NAME} -c test | grep EXIT_CODE)
EXIT_CODE=$(oc logs -n ${CHE_NAMESPACE} ${TEST_POD_NAME} -c oauth-test | grep EXIT_CODE)
if [[ ${EXIT_CODE} != "+ EXIT_CODE=0" ]]; then
echo "####### [ERROR] GUI test failed. Job failed. #######
###### Cause possible: PR code regress or service is changed. Need to investigate it. #######"
echo "[ERROR] Factory OAuth test failed. Job failed."
exit 1
fi
echo "======= [INFO] Job completed successfully. ======="
echo "[INFO] Job completed successfully."
}
testCloneGitRepoNoProjectExists() {
@ -553,11 +468,10 @@ testCloneGitRepoNoProjectExists() {
OCP_USER_NAMESPACE=$4
runTestWorkspaceWithGitRepoUrl ${WS_NAME} ${PROJECT_NAME} ${GIT_REPO_URL} ${OCP_USER_NAMESPACE}
echo "------- [INFO] Check the private repository is NOT cloned with NO PAT/OAuth setup. -------"
echo "[INFO] Check the private repository is NOT cloned with NO PAT/OAuth setup"
testProjectIsCloned ${PROJECT_NAME} ${OCP_USER_NAMESPACE} && \
{ echo "####### [ERROR] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} should NOT be present. #######
####### Cause possible: PR code regress or service is changed. Need to investigate it. #######" && exit 1; }
echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} is NOT present. This is EXPECTED. ======="
{ echo "[ERROR] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} should NOT be present" && exit 1; }
echo "[INFO] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} is NOT present. This is EXPECTED"
}
# Test that the repository is cloned when PAT, OAuth or SSH is configured
@ -568,10 +482,9 @@ testCloneGitRepoProjectShouldExists() {
OCP_USER_NAMESPACE=$4
runTestWorkspaceWithGitRepoUrl ${WS_NAME} ${PROJECT_NAME} ${GIT_REPO_URL} ${OCP_USER_NAMESPACE}
echo "------- [INFO] Check the repository is cloned. -------"
echo "[INFO] Check the repository is cloned"
testProjectIsCloned ${PROJECT_NAME} ${OCP_USER_NAMESPACE} || \
{ echo "####### [ERROR] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} should be present. #######
###### Cause possible: PR code regress or service is changed. Need to investigate it. #######" && exit 1; }
{ echo "[ERROR] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} should be present." && exit 1; }
}
setupTestEnvironment() {

View File

@ -1,77 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: che-smoke-test
namespace: CHE-NAMESPACE
spec:
volumes:
- name: test-run-results
- name: ffmpeg-video
- name: dshm
emptyDir:
medium: Memory
containers:
# container containing the tests
- name: test
image: quay.io/eclipse/che-e2e:next
imagePullPolicy: Always
env:
- name: VIDEO_RECORDING
value: "true"
- name: TEST_SUITE
value: "test"
- name: TS_SELENIUM_EDITOR
value: "che-code"
- name: USERSTORY
value: "SmokeTest"
- name: NODE_TLS_REJECT_UNAUTHORIZED
value: "0"
- name: TS_SELENIUM_BASE_URL
value: "CHE_URL"
- name: TS_SELENIUM_LOG_LEVEL
value: "TRACE"
- name: TS_SELENIUM_OCP_USERNAME
value: "OCP_USER_NAME"
- name: TS_SELENIUM_OCP_PASSWORD
value: "OCP_USER_PASSWORD"
- name: TS_SELENIUM_VALUE_OPENSHIFT_OAUTH
value: "true"
- name: TS_OCP_LOGIN_PAGE_PROVIDER_TITLE
value: "htpasswd"
- name: DELETE_WORKSPACE_ON_FAILED_TEST
value: "true"
- name: TS_SELENIUM_START_WORKSPACE_TIMEOUT
value: "360000"
- name: TS_IDE_LOAD_TIMEOUT
value: "40000"
volumeMounts:
- name: test-run-results
mountPath: /tmp/e2e/report/
- name: ffmpeg-video
mountPath: /tmp/ffmpeg_report/
- name: dshm
mountPath: /dev/shm
resources:
requests:
memory: "3Gi"
cpu: "2"
limits:
memory: "4Gi"
cpu: "2"
# Download results
- name: download-reports
image: eeacms/rsync
imagePullPolicy: IfNotPresent
volumeMounts:
- name: test-run-results
mountPath: /tmp/e2e/report/
- name: ffmpeg-video
mountPath: /tmp/ffmpeg_report/
resources:
command: ["sh"]
args:
[
"-c",
"while true; if [[ -f /tmp/done ]]; then exit 0; fi; do sleep 1; done",
]
restartPolicy: Never

View File

@ -12,7 +12,7 @@ spec:
medium: Memory
containers:
# container containing the tests
- name: test
- name: oauth-test
image: quay.io/eclipse/che-e2e:next
imagePullPolicy: Always
env:

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_RAW_PATH_URL=${PUBLIC_REPO_RAW_PATH_URL:-"https://dev.azure.com/chepullreq1/che-pr-public/_apis/git/repositories/public-repo/items?path=/devfile.yaml"}
export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://dev.azure.com/chepullreq1/che-pr-private/_apis/git/repositories/private-repo/items?path=/devfile.yaml"}

View File

@ -17,8 +17,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_SSH_URL=${PUBLIC_REPO_SSH_URL:-"git@ssh.dev.azure.com:v3/chepullreq1/che-pr-public/public-repo"}
export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@ssh.dev.azure.com:v3/chepullreq1/che-pr-private/private-repo"}

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://chepullreq1@dev.azure.com/chepullreq1/che-pr-public/_git/public-repo"}
export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://dev.azure.com/chepullreq1/che-pr-private/_git/private-repo"}

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://chepullreq1@dev.azure.com/chepullreq1/che-pr-public/_git/public-repo"}
export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://dev.azure.com/chepullreq1/che-pr-private/_git/private-repo"}
export GIT_PROVIDER_TYPE=${GIT_PROVIDER_TYPE:-"azure-devops"}
@ -36,24 +34,24 @@ setupPersonalAccessToken ${GIT_PROVIDER_TYPE} ${GIT_PROVIDER_URL} ${AZURE_PAT}
requestProvisionNamespace
testFactoryResolverWithPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL}
echo "------- [INFO] Check clone public repository with PAT setup -------"
echo "[INFO] Check clone public repository with PAT setup"
testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE}
testGitCredentialsData ${USER_CHE_NAMESPACE} ${AZURE_PAT} ${GIT_PROVIDER_URL}
deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}
echo "------- [INFO] Check clone private repository with PAT setup -------"
echo "[INFO] Check clone private repository with PAT setup"
testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE}
testGitCredentialsData ${USER_CHE_NAMESPACE} ${AZURE_PAT} ${GIT_PROVIDER_URL}
deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}
echo "------- [INFO] Check clone private repository by raw devfile URL with PAT setup -------"
echo "[INFO] Check clone private repository by raw devfile URL with PAT setup"
testFactoryResolverResponse ${PRIVATE_REPO_RAW_PATH_URL} 200
testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE}
deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}
setupSSHKeyPairs "${AZURE_PRIVATE_KEY}" "${AZURE_PUBLIC_KEY}"
echo "------- [INFO] Check clone private repository by SSH URL with PAT setup -------"
echo "[INFO] Check clone private repository by SSH URL with PAT setup"
testFactoryResolverResponse ${PRIVATE_REPO_SSH_URL} 200
testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_SSH_URL} ${USER_CHE_NAMESPACE}
deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_RAW_PATH_URL=${PUBLIC_REPO_RAW_PATH_URL:-"https://bitbucket.org/chepullreq/public-repo/raw/746000bd63a54eaf8ea8aba9dfe6620e5c6c61d7/devfile.yaml"}
export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://bitbucket.org/chepullreq/private-repo/raw/80b183d27c6c533462128b0c092208aad73b2906/devfile.yaml"}

View File

@ -17,8 +17,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_SSH_URL=${PUBLIC_REPO_SSH_URL:-"git@bitbucket.org:chepullreq/public-repo.git"}
export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@bitbucket.org:chepullreq/private-repo.git"}

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://chepullreq1@bitbucket.org/chepullreq/public-repo.git"}
export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://chepullreq1@bitbucket.org/chepullreq/private-repo.git"}

View File

@ -1,32 +0,0 @@
#!/bin/bash
#
# Copyright (c) 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
#
# exit immediately when a command fails
set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export TEST_POD_NAME=${TEST_POD_NAME:-"che-smoke-test"}
# import common test functions
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
source "${SCRIPT_DIR}"/common.sh
trap "collectLogs" EXIT SIGINT
provisionOpenShiftOAuthUser
createCustomResourcesFile
deployChe
startSmokeTest

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_SSH_URL=${PUBLIC_REPO_SSH_URL:-"git@gitea.com:chepullreq1/public-repo.git"}
export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@gitea.com:chepullreq1/private-repo.git"}
export PUBLIC_REPO_RAW_PATH_URL=${PUBLIC_REPO_RAW_PATH_URL:-"https://gitea.com/chepullreq1/public-repo/raw/branch/main/devfile.yaml"}

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_RAW_PATH_URL:-"https://${GITEA_PAT}@gitea.com/chepullreq1/private-repo/raw/branch/main/devfile.yaml"}
# import common test functions

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_RAW_PATH_URL=${PUBLIC_REPO_RAW_PATH_URL:-"https://raw.githubusercontent.com/chepullreq1/public-repo/main/devfile.yaml"}
export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://raw.githubusercontent.com/chepullreq1/private-repo/main/devfile.yaml"}

View File

@ -17,8 +17,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_SSH_URL=${PUBLIC_REPO_SSH_URL:-"git@github.com:chepullreq1/public-repo.git"}
export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@github.com:chepullreq1/private-repo.git"}

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://github.com/chepullreq1/public-repo.git"}
export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://github.com/chepullreq1/private-repo.git"}

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://github.com/chepullreq1/public-repo.git"}
export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://github.com/chepullreq1/private-repo.git"}
export GIT_PROVIDER_TYPE=${GIT_PROVIDER_TYPE:-"github"}
@ -36,24 +34,24 @@ setupPersonalAccessToken ${GIT_PROVIDER_TYPE} ${GIT_PROVIDER_URL} ${GITHUB_PAT}
requestProvisionNamespace
testFactoryResolverWithPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL}
echo "------- [INFO] Check clone public repository with PAT setup -------"
echo "[INFO] Check clone public repository with PAT setup"
testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE}
testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITHUB_PAT} ${GIT_PROVIDER_URL}
deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}
echo "------- [INFO] Check clone private repository with PAT setup -------"
echo "[INFO] Check clone private repository with PAT setup"
testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE}
testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITHUB_PAT} ${GIT_PROVIDER_URL}
deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}
echo "------- [INFO] Check clone private repository by raw devfile URL with PAT setup -------"
echo "[INFO] Check clone private repository by raw devfile URL with PAT setup"
testFactoryResolverResponse ${PRIVATE_REPO_RAW_PATH_URL} 200
testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE}
deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}
setupSSHKeyPairs "${GITHUB_PRIVATE_KEY}" "${GITHUB_PUBLIC_KEY}"
echo "------- [INFO] Check clone private repository by SSH URL with PAT setup -------"
echo "[INFO] Check clone private repository by SSH URL with PAT setup"
testFactoryResolverResponse ${PRIVATE_REPO_SSH_URL} 200
testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_SSH_URL} ${USER_CHE_NAMESPACE}
deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_RAW_PATH_URL=${PUBLIC_REPO_RAW_PATH_URL:-"https://gitlab.com/chepullreq1/public-repo/-/raw/main/devfile.yaml"}
export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://gitlab.com/chepullreq1/private-repo/-/raw/main/devfile.yaml"}

View File

@ -17,8 +17,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_SSH_URL=${PUBLIC_REPO_SSH_URL:-"git@gitlab.com:chepullreq1/public-repo.git"}
export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@gitlab.com:chepullreq1/private-repo.git"}

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://gitlab.com/chepullreq1/public-repo.git"}
export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://gitlab.com/chepullreq1/private-repo.git"}

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export TEST_POD_NAME=${TEST_POD_NAME:-"oauth-factory-test"}
export GIT_PROVIDER_TYPE=${GIT_PROVIDER_TYPE:-"gitlab"}
export GIT_PROVIDER_URL=${GIT_PROVIDER_URL:-"https://gitlab-gitlab-system.apps.git.crw-qe.com"}

View File

@ -16,8 +16,6 @@ set -ex
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail
echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======="
export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://gitlab.com/chepullreq1/public-repo.git"}
export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://gitlab.com/chepullreq1/private-repo.git"}
export GIT_PROVIDER_TYPE=${GIT_PROVIDER_TYPE:-"gitlab"}
@ -36,24 +34,24 @@ setupPersonalAccessToken ${GIT_PROVIDER_TYPE} ${GIT_PROVIDER_URL} ${GITLAB_PAT}
requestProvisionNamespace
testFactoryResolverWithPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL}
echo "------- [INFO] Check clone public repository with PAT setup -------"
echo "[INFO] Check clone public repository with PAT setup"
testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE}
testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITLAB_PAT} ${GIT_PROVIDER_URL}
deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}
echo "------- [INFO] Check clone private repository with PAT setup -------"
echo "[INFO] Check clone private repository with PAT setup"
testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE}
testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITLAB_PAT} ${GIT_PROVIDER_URL}
deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}
echo "------- [INFO] Check clone private repository by raw devfile URL with PAT setup -------"
echo "[INFO] Check clone private repository by raw devfile URL with PAT setup"
testFactoryResolverResponse ${PRIVATE_REPO_RAW_PATH_URL} 200
testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE}
deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}
setupSSHKeyPairs "${GITLAB_PRIVATE_KEY}" "${GITLAB_PUBLIC_KEY}"
echo "------- [INFO] Check clone private repository by SSH URL with PAT setup -------"
echo "[INFO] Check clone private repository by SSH URL with PAT setup"
testFactoryResolverResponse ${PRIVATE_REPO_SSH_URL} 200
testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_SSH_URL} ${USER_CHE_NAMESPACE}
deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE}

5
.github/CODEOWNERS vendored
View File

@ -1,8 +1,11 @@
# Global Owners
* @SDawley @ibuziuk @vinokurig @tolusha
* @l0rd @ibuziuk @vinokurig @nickboldt @tolusha
# che.properties file is quasi-documentation, so in future we might grant access to this
# assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/** @osslate
# workspace runtime infrastructure implementations
infrastructures/** @amisevsk
# deploy
deploy/** @tolusha @dkwon17

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-assembly-parent</artifactId>
<groupId>org.eclipse.che</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>assembly-che-tomcat</artifactId>
<packaging>jar</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-assembly-parent</artifactId>
<groupId>org.eclipse.che</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>assembly-main</artifactId>
<packaging>pom</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-assembly-parent</artifactId>
<groupId>org.eclipse.che</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>assembly-root-war</artifactId>
<packaging>war</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-assembly-parent</artifactId>
<groupId>org.eclipse.che</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>assembly-swagger-war</artifactId>
<packaging>war</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-assembly-parent</artifactId>
<groupId>org.eclipse.che</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>assembly-wsmaster-war</artifactId>
<packaging>war</packaging>
@ -91,6 +91,10 @@
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-account</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth</artifactId>
@ -155,6 +159,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-logger</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-metrics</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-ssh</artifactId>
@ -191,6 +199,18 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-schedule</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-db</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-db-vendor-h2</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-db-vendor-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-logback</artifactId>
@ -335,6 +355,10 @@
<groupId>org.everrest</groupId>
<artifactId>everrest-integration-guice</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.jgroups</groupId>
<artifactId>jgroups</artifactId>

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2024 Red Hat, Inc.
* Copyright (c) 2012-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/
@ -26,6 +26,7 @@ import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.SigningKeyResolver;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.eclipse.che.api.core.notification.RemoteSubscriptionStorage;
import org.eclipse.che.api.core.rest.CheJsonProvider;
import org.eclipse.che.api.core.rest.MessageBodyAdapter;
@ -52,6 +53,7 @@ import org.eclipse.che.api.factory.server.github.GithubScmFileResolver;
import org.eclipse.che.api.factory.server.github.GithubScmFileResolverSecond;
import org.eclipse.che.api.factory.server.gitlab.GitlabFactoryParametersResolver;
import org.eclipse.che.api.factory.server.gitlab.GitlabScmFileResolver;
import org.eclipse.che.api.metrics.WsMasterMetricsModule;
import org.eclipse.che.api.system.server.ServiceTermination;
import org.eclipse.che.api.system.server.SystemModule;
import org.eclipse.che.api.user.server.NotImplementedTokenValidator;
@ -84,10 +86,13 @@ import org.eclipse.che.api.workspace.server.spi.provision.env.WorkspaceNameEnvVa
import org.eclipse.che.api.workspace.server.spi.provision.env.WorkspaceNamespaceNameEnvVarProvider;
import org.eclipse.che.api.workspace.server.wsplugins.ChePluginsApplier;
import org.eclipse.che.commons.observability.deploy.ExecutorWrapperModule;
import org.eclipse.che.core.db.DBTermination;
import org.eclipse.che.core.db.schema.SchemaInitializer;
import org.eclipse.che.core.tracing.metrics.TracingMetricsModule;
import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.multiuser.api.authentication.commons.token.HeaderRequestTokenExtractor;
import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor;
import org.eclipse.che.multiuser.api.permission.server.AdminPermissionInitializer;
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
import org.eclipse.che.multiuser.api.permission.server.PermissionCheckerImpl;
import org.eclipse.che.multiuser.api.workspace.activity.MultiUserWorkspaceActivityModule;
@ -122,6 +127,7 @@ import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftInfrastructur
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment;
import org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth.KeycloakProviderConfigFactory;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.flywaydb.core.internal.util.PlaceholderReplacer;
/** @author andrew00x */
@DynaModule
@ -137,10 +143,18 @@ public class WsMasterModule extends AbstractModule {
}
// db related components modules
install(new org.eclipse.che.account.api.AccountModule());
install(new org.eclipse.che.api.ssh.server.jpa.SshJpaModule());
install(new org.eclipse.che.api.core.jsonrpc.impl.JsonRpcModule());
install(new org.eclipse.che.api.core.websocket.impl.WebSocketModule());
// db configuration
bind(SchemaInitializer.class)
.to(org.eclipse.che.core.db.schema.impl.flyway.FlywaySchemaInitializer.class);
bind(org.eclipse.che.core.db.DBInitializer.class).asEagerSingleton();
bind(PlaceholderReplacer.class)
.toProvider(org.eclipse.che.core.db.schema.impl.flyway.PlaceholderReplacerProvider.class);
// factory
bind(FactoryAcceptValidator.class)
.to(org.eclipse.che.api.factory.server.impl.FactoryAcceptValidatorImpl.class);
@ -199,7 +213,9 @@ public class WsMasterModule extends AbstractModule {
install(new DevfileModule());
bind(WorkspaceEntityProvider.class);
bind(org.eclipse.che.api.workspace.server.TemporaryWorkspaceRemover.class);
bind(org.eclipse.che.api.workspace.server.WorkspaceService.class);
bind(org.eclipse.che.api.devfile.server.DevfileService.class);
bind(org.eclipse.che.api.devfile.server.UserDevfileEntityProvider.class);
install(new FactoryModuleBuilder().build(ServersCheckerFactory.class));
@ -266,6 +282,10 @@ public class WsMasterModule extends AbstractModule {
terminationMultiBinder
.addBinding()
.to(org.eclipse.che.api.system.server.CronThreadPullTermination.class);
terminationMultiBinder
.addBinding()
.to(org.eclipse.che.api.workspace.server.hc.probe.ProbeSchedulerTermination.class);
bind(DBTermination.class);
final Map<String, String> persistenceProperties = new HashMap<>();
persistenceProperties.put(PersistenceUnitProperties.TARGET_SERVER, "None");
@ -308,6 +328,7 @@ public class WsMasterModule extends AbstractModule {
}
if (Boolean.valueOf(System.getenv("CHE_METRICS_ENABLED"))) {
install(new org.eclipse.che.core.metrics.MetricsModule());
install(new WsMasterMetricsModule());
install(new InfrastructureMetricsModule());
} else {
install(new org.eclipse.che.core.metrics.NoopMetricsModule());
@ -348,6 +369,9 @@ public class WsMasterModule extends AbstractModule {
persistenceProperties.put(
PersistenceUnitProperties.EXCEPTION_HANDLER_CLASS,
"org.eclipse.che.core.db.postgresql.jpa.eclipselink.PostgreSqlExceptionHandler");
bind(DataSource.class).toProvider(org.eclipse.che.core.db.JndiDataSourceProvider.class);
install(new org.eclipse.che.multiuser.api.permission.server.jpa.SystemPermissionsJpaModule());
install(
new org.eclipse.che.multiuser.permission.workspace.server.WorkspaceApiPermissionsModule());
@ -396,6 +420,7 @@ public class WsMasterModule extends AbstractModule {
bind(OAuthAPI.class).to(EmbeddedOAuthAPI.class).asEagerSingleton();
}
bind(AdminPermissionInitializer.class).asEagerSingleton();
install(new MachineAuthModule());
// User and profile - use profile from keycloak and other stuff is JPA

View File

@ -91,6 +91,10 @@ che.workspace.server.liveness_probes=wsagent/http,exec-agent/http,terminal,theia
# The default is: 10MB=10485760.
che.workspace.startup_debug_log_limit_bytes=10485760
# If set to `true`, 'stop-workspace' role with the edit privileges is granted to the 'che' ServiceAccount if OpenShift OAuth is enabled.
# This configuration is mainly required for workspace idling when the OpenShift OAuth is enabled.
che.workspace.stop.role.enabled=true
# Specifies whether {prod-short} is deployed with DevWorkspaces enabled.
# This property is set by the {prod-short} Operator if it also installed the support for DevWorkspaces.
# This property is used to advertise this fact to the {prod-short} dashboard.
@ -610,7 +614,7 @@ che.factory.default_devfile_filenames=devfile.yaml,.devfile.yaml
### Devfile defaults
# Editor used for factories that are created from a remote Git repository
# which doesn't contain any {prod-short}-specific workspace descriptor.
che.factory.default_editor=eclipse/che-theia/next
che.factory.default_editor=eclipse/che-theia/latest
# File size limit for the URL fetcher which fetches files from the SCM repository.
che.factory.scm_file_fetcher_limit_bytes=102400
@ -622,7 +626,7 @@ che.factory.devfile2_files_resolution_list=.che/che-editor.yaml,.che/che-theia-p
# Default editor that should be provisioned into Devfile if there is no specified editor.
# The format is `editorPublisher/editorName/editorVersion` value.
# A `NULL` or absence of value means that default editor should not be provisioned.
che.workspace.devfile.default_editor=eclipse/che-theia/next
che.workspace.devfile.default_editor=eclipse/che-theia/latest
# Default plug-ins which should be provisioned for the default editor.
# All plug-ins from this list that aren't explicitly mentioned in the user-defined devfile

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012-2024 Red Hat, Inc.
* Copyright (c) 2012-2021 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/
@ -37,7 +37,6 @@ import org.reflections.scanners.FieldAnnotationsScanner;
import org.reflections.scanners.MethodAnnotationsScanner;
import org.reflections.scanners.MethodParameterScanner;
import org.testng.Assert;
import org.testng.annotations.Ignore;
import org.testng.annotations.Test;
public class IntegrityConfigurationTest {
@ -126,7 +125,6 @@ public class IntegrityConfigurationTest {
}
@Test
@Ignore
public void shouldNotDeclareUnused() {
Reflections reflections =
new Reflections(

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-server</artifactId>
<groupId>org.eclipse.che</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>che-assembly-parent</artifactId>

View File

@ -10,7 +10,7 @@
#
# https://access.redhat.com/containers/?tab=tags#/registry.access.redhat.com/ubi8-minimal
FROM registry.access.redhat.com/ubi8-minimal:8.9-1161
FROM registry.access.redhat.com/ubi8-minimal:8.9-1029
USER root
ENV CHE_HOME=/home/user/eclipse-che
ENV JAVA_HOME=/usr/lib/jvm/jre

View File

@ -10,7 +10,7 @@
#
# https://access.redhat.com/containers/?tab=tags#/registry.access.redhat.com/ubi8-minimal
FROM ubi8-minimal:8.9-1161
FROM ubi8-minimal:8.9-1029
USER root
ENV CHE_HOME=/home/user/devspaces
ENV JAVA_HOME=/usr/lib/jvm/jre

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-api-core</artifactId>
<packaging>jar</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-api-dto-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-api-dto</artifactId>
<packaging>jar</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-api-model</artifactId>
<packaging>jar</packaging>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.80.0</version>
</parent>
<artifactId>che-core-db-vendor-h2</artifactId>
<name>Che Core :: Commons :: DB :: Vendor H2</name>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-db</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>jakarta.persistence</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.h2.jpa.eclipselink;
import java.sql.SQLException;
import org.eclipse.che.core.db.jpa.DuplicateKeyException;
import org.eclipse.che.core.db.jpa.IntegrityConstraintViolationException;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.ExceptionHandler;
/**
* Rethrows vendor specific exceptions as common exceptions. See <a
* href="http://www.h2database.com/javadoc/org/h2/api/ErrorCode.html">H2 error codes</a>.
*
* @author Yevhenii Voevodin
*/
public class H2ExceptionHandler implements ExceptionHandler {
public Object handleException(RuntimeException exception) {
if (exception instanceof DatabaseException && exception.getCause() instanceof SQLException) {
final SQLException sqlEx = (SQLException) exception.getCause();
switch (sqlEx.getErrorCode()) {
case 23505:
throw new DuplicateKeyException(exception.getMessage(), exception);
case 23506:
throw new IntegrityConstraintViolationException(exception.getMessage(), exception);
}
}
throw exception;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2012-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 org.eclipse.che.core.db.h2.jpa.eclipselink;
import static com.google.common.base.MoreObjects.firstNonNull;
import org.eclipse.che.core.db.JNDIDataSourceFactory;
public class H2SQLJndiDataSourceFactory extends JNDIDataSourceFactory {
private static final String DEFAULT_USERNAME = "username";
private static final String DEFAULT_PASSWORD = "password";
private static final String DEFAULT_URL = "jdbc:h2:file:/data/h2";
private static final String DEFAULT_DRIVER__CLASS__NAME = "org.h2.Driver";
private static final String DEFAULT_MAX__TOTAL = "20";
private static final String DEFAULT_MAX__IDLE = "2";
private static final String DEFAULT_MAX__WAIT__MILLIS = "-1";
public H2SQLJndiDataSourceFactory() throws Exception {
super(
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_USERNAME")), DEFAULT_USERNAME),
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_PASSWORD")), DEFAULT_PASSWORD),
firstNonNull(nullStringToNullReference(System.getenv("CHE_JDBC_H2_URL")), DEFAULT_URL),
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_DRIVER__CLASS__NAME")),
DEFAULT_DRIVER__CLASS__NAME),
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_MAX__TOTAL")), DEFAULT_MAX__TOTAL),
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_MAX__IDLE")), DEFAULT_MAX__IDLE),
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_MAX__WAIT__MILLIS")),
DEFAULT_MAX__WAIT__MILLIS));
;
}
}

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.80.0</version>
</parent>
<artifactId>che-core-db-vendor-mysql</artifactId>
<name>Che Core :: Commons :: DB :: Vendor MySQL</name>
<dependencies>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-db</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>jakarta.persistence</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.mysql.jpa.eclipselink;
import java.sql.SQLException;
import org.eclipse.che.core.db.jpa.DuplicateKeyException;
import org.eclipse.che.core.db.jpa.IntegrityConstraintViolationException;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.ExceptionHandler;
/**
* Rethrows vendor specific exceptions as common exceptions. See <a
* href="https://dev.mysql.com/doc/refman/8.0/en/error-messages-server.html">MySQL error codes</a>.
*
* @author Barry Dresdner
*/
public class MySqlExceptionHandler implements ExceptionHandler {
@Override
public Object handleException(RuntimeException exception) {
if (exception instanceof DatabaseException && exception.getCause() instanceof SQLException) {
final SQLException sqlEx = (SQLException) exception.getCause();
switch (sqlEx.getErrorCode()) {
case 1062:
throw new DuplicateKeyException(exception.getMessage(), exception);
case 1452:
throw new IntegrityConstraintViolationException(exception.getMessage(), exception);
}
}
throw exception;
}
}

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.80.0</version>
</parent>
<artifactId>che-core-db-vendor-postgresql</artifactId>
<name>Che Core :: Commons :: DB :: Vendor PostgreSQL</name>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-db</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>jakarta.persistence</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.postgresql;
import static com.google.common.base.MoreObjects.firstNonNull;
import org.eclipse.che.core.db.JNDIDataSourceFactory;
/**
* Environment params based JNDI data source factory for Postgres.
*
* @author Sergii Kabashniuk
*/
public class PostgreSQLJndiDataSourceFactory extends JNDIDataSourceFactory {
private static final String DEFAULT_USERNAME = "pgche";
private static final String DEFAULT_PASSWORD = "pgchepassword";
private static final String DEFAULT_URL = "jdbc:postgresql://postgres:5432/dbche";
private static final String DEFAULT_DRIVER__CLASS__NAME = "org.postgresql.Driver";
private static final String DEFAULT_MAX__TOTAL = "20";
private static final String DEFAULT_MAX__IDLE = "2";
private static final String DEFAULT_MAX__WAIT__MILLIS = "-1";
public PostgreSQLJndiDataSourceFactory() throws Exception {
super(
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_USERNAME")), DEFAULT_USERNAME),
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_PASSWORD")), DEFAULT_PASSWORD),
firstNonNull(nullStringToNullReference(System.getenv("CHE_JDBC_URL")), DEFAULT_URL),
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_DRIVER__CLASS__NAME")),
DEFAULT_DRIVER__CLASS__NAME),
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_MAX__TOTAL")), DEFAULT_MAX__TOTAL),
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_MAX__IDLE")), DEFAULT_MAX__IDLE),
firstNonNull(
nullStringToNullReference(System.getenv("CHE_JDBC_MAX__WAIT__MILLIS")),
DEFAULT_MAX__WAIT__MILLIS));
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.postgresql.jpa.eclipselink;
import java.sql.SQLException;
import org.eclipse.che.core.db.jpa.DuplicateKeyException;
import org.eclipse.che.core.db.jpa.IntegrityConstraintViolationException;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.ExceptionHandler;
/**
* Rethrows vendor specific exceptions as common exceptions. See <a
* href="https://www.postgresql.org/docs/9.4/static/errcodes-appendix.html">PostgreSQL error
* codes</a>.
*
* @author Yevhenii Voevodin
* @author Sergii Kabashniuk
*/
public class PostgreSqlExceptionHandler implements ExceptionHandler {
public Object handleException(RuntimeException exception) {
if (exception instanceof DatabaseException && exception.getCause() instanceof SQLException) {
final SQLException sqlEx = (SQLException) exception.getCause();
switch (sqlEx.getSQLState()) {
case "23505":
throw new DuplicateKeyException(exception.getMessage(), exception);
case "23503":
throw new IntegrityConstraintViolationException(exception.getMessage(), exception);
}
}
throw exception;
}
}

113
core/che-core-db/pom.xml Normal file
View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.80.0</version>
</parent>
<artifactId>che-core-db</artifactId>
<name>Che Core :: Commons :: DB</name>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-persist</artifactId>
</dependency>
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-util</artifactId>
</dependency>
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-jdbc</artifactId>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-inject</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.extension</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>jakarta.persistence</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db;
/**
* Defines common database error codes which should be used throughout the application in preference
* to vendor specific error codes.
*
* @author Yevhenii Voevodin
*/
public enum DBErrorCode {
/** When database error can't be described with one of the other values of this enumeration. */
UNDEFINED(-1),
/**
* When any of the unique constraints is violated e.g. duplicate key or unique index violation.
*/
DUPLICATE_KEY(1),
/** When entity referenced foreign key does not exist */
INTEGRITY_CONSTRAINT_VIOLATION(2);
private final int code;
DBErrorCode(int code) {
this.code = code;
}
/** Returns the code of this error. */
public int getCode() {
return code;
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.persistence.EntityManagerFactory;
import org.eclipse.che.core.db.jpa.JpaInitializer;
import org.eclipse.che.core.db.jpa.eclipselink.GuiceEntityListenerInjectionManager;
import org.eclipse.che.core.db.schema.SchemaInitializationException;
import org.eclipse.che.core.db.schema.SchemaInitializer;
import org.eclipse.persistence.internal.sessions.AbstractSession;
/**
* Initializes database components.
*
* <p>Those components which require any persistence operations on their bootstrap have to depend on
* this component. For example:
*
* <pre>
* class StackExistsChecker {
*
* &#064;@Inject
* &#064;SuppressWarnings("unused")
* private DBInitializer dbInitializer;
*
* &#064;PostConstruct
* public void check() {
* ....
* }
* }
* </pre>
*
* In this way it is guaranteed that all database related components will be appropriately
* initialized before {@code check} method is executed.
*
* @author Yevhenii Voevodin
*/
@Singleton
public class DBInitializer {
/**
* when value for this key true, then its mean that db is initialized at first time, otherwise db
* was previously initialized
*/
public static final String BARE_DB_INIT_PROPERTY_NAME = "bare_database_init";
private final Map<String, String> initProperties;
@Inject
public DBInitializer(SchemaInitializer schemaInitializer, JpaInitializer jpaInitializer)
throws SchemaInitializationException {
// schema must be initialized before any other component that may interact with database
initProperties = ImmutableMap.copyOf(schemaInitializer.init());
// jpa initialization goes next
jpaInitializer.init();
}
@Inject
public void setUpInjectionManager(
GuiceEntityListenerInjectionManager injManager, EntityManagerFactory emFactory) {
final AbstractSession session = emFactory.unwrap(AbstractSession.class);
session.setInjectionManager(injManager);
}
/** Returns map of properties which represents state of database while initialization process */
public Map<String, String> getInitProperties() {
return initProperties;
}
/**
* Returns true only if database was initialized at first time otherwise false would be returned
*/
public boolean isBareInit() {
return Boolean.parseBoolean(initProperties.get(BARE_DB_INIT_PROPERTY_NAME));
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2012-2021 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 org.eclipse.che.core.db;
import com.google.inject.Inject;
import com.google.inject.persist.PersistService;
import java.lang.reflect.Field;
import javax.inject.Singleton;
import javax.persistence.EntityManagerFactory;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.coordination.jgroups.JGroupsRemoteConnection;
import org.eclipse.persistence.sessions.coordination.CommandManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Stops {@link PersistService} when a system is ready to shutdown.
*
* @author Anton Korneta
*/
@Singleton
public class DBTermination {
private static final Logger LOG = LoggerFactory.getLogger(DBTermination.class);
private final PersistService persistService;
private final EntityManagerFactory emFactory;
@Inject
public DBTermination(PersistService persistService, EntityManagerFactory emFactory) {
this.persistService = persistService;
this.emFactory = emFactory;
}
/** Stops {@link PersistService}. Any DB operations are impossible after that. */
public void terminate() {
try {
LOG.info("Stopping persistence service.");
fixJChannelClosing(emFactory);
persistService.stop();
} catch (RuntimeException ex) {
LOG.error("Failed to stop persistent service. Cause: " + ex.getMessage());
}
}
/**
* This method is hack that changes value of {@link JGroupsRemoteConnection#isLocal} to false.
* This is needed to close the JGroups EclipseLinkCommandChannel and as result gracefully stop of
* the system.<br>
* For more details see {@link JGroupsRemoteConnection#closeInternal()}
*
* <p>The corresponding eclipse-link extension issue
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=534148
*/
private void fixJChannelClosing(EntityManagerFactory emFactory) {
try {
final AbstractSession session = emFactory.unwrap(AbstractSession.class);
CommandManager commandManager = session.getCommandManager();
if (commandManager == null) {
// not cluster mode
return;
}
final JGroupsRemoteConnection conn =
(JGroupsRemoteConnection) commandManager.getTransportManager().getConnectionToLocalHost();
final Field isLocal = conn.getClass().getDeclaredField("isLocal");
isLocal.setAccessible(true);
isLocal.set(conn, false);
} catch (IllegalAccessException | NoSuchFieldException ex) {
LOG.error(
"Failed to change JGroupsRemoteConnection#isLocal. This may prevent the graceful stop of "
+ "the system because EclipseLinkCommandChannel will not be closed. Cause: "
+ ex.getMessage());
}
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db;
import static org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory.createDataSource;
import static org.eclipse.che.core.db.TracingDataSource.wrapWithTracingIfEnabled;
import java.util.Hashtable;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import javax.sql.DataSource;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract JNDI factory that constructs {@link BasicDataSource} objects from the given params.
* Should not be used directly and must be subclassed to provide instantiation params from needful
* source.
*
* @author Sergii Kabashniuk
*/
public abstract class JNDIDataSourceFactory implements ObjectFactory {
private static final Logger LOG = LoggerFactory.getLogger(JNDIDataSourceFactory.class);
private final DataSource dataSource;
public JNDIDataSourceFactory(
String userName,
String password,
String url,
String driverClassName,
String maxTotal,
String maxIdle,
String maxWaitMillis)
throws Exception {
Properties poolConfigurationProperties = new Properties();
poolConfigurationProperties.setProperty("username", userName);
poolConfigurationProperties.setProperty("password", password);
poolConfigurationProperties.setProperty("url", url);
poolConfigurationProperties.setProperty("driverClassName", driverClassName);
poolConfigurationProperties.setProperty("maxTotal", maxTotal);
poolConfigurationProperties.setProperty("maxIdle", maxIdle);
poolConfigurationProperties.setProperty("maxWaitMillis", maxWaitMillis);
dataSource = wrapWithTracingIfEnabled(createDataSource(poolConfigurationProperties));
}
@Override
public Object getObjectInstance(
Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
LOG.info(
"This={} obj={} name={} Context={} environment={}", this, obj, name, nameCtx, environment);
return dataSource;
}
/**
* Util method to convert string {@code "NULL"} to null reference. Allows to set string {@code
* "NULL"} as a value of the property instead of making sure it is unset as it is done in {@link
* org.eclipse.che.inject.CheBootstrap}
*
* @param value value to transform if needed
* @return null or passed value
*/
protected static String nullStringToNullReference(String value) {
return "NULL".equals(value) ? null : value;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db;
import com.google.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
/**
* Provides data source based on jndi resource name.
*
* @author Yevhenii Voevodin
*/
public class JndiDataSourceProvider implements Provider<DataSource> {
@Inject
@Named("jndi.datasource.name")
private String name;
@Override
public DataSource get() {
try {
final InitialContext context = new InitialContext();
return (DataSource) context.lookup(name);
} catch (NamingException x) {
throw new IllegalStateException(x.getLocalizedMessage(), x);
}
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2012-2021 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 org.eclipse.che.core.db;
import com.google.common.annotations.Beta;
import io.opentracing.contrib.jdbc.ConnectionInfo;
import io.opentracing.contrib.jdbc.TracingConnection;
import io.opentracing.contrib.jdbc.parser.URLParser;
import io.opentracing.util.GlobalTracer;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.sql.DataSource;
import org.slf4j.LoggerFactory;
/**
* Adding tracing support for existing @{@link javax.sql.DataSource}. DbType and DbUser information
* omitted in traces. Traced are made only if active span exists. Prerequisites of using this class
* is that @{@link io.opentracing.Tracer} should be set in @{@link io.opentracing.util.GlobalTracer}
*
* @author Sergii Kabashniuk
*/
@Beta
public class TracingDataSource implements DataSource {
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(TracingDataSource.class);
private final DataSource delegate;
private final ConnectionInfo connectionInfo;
public TracingDataSource(DataSource delegate) {
this.delegate = delegate;
try (Connection connection = delegate.getConnection()) {
connectionInfo = URLParser.parser(connection.getMetaData().getURL());
LOG.debug(
"URL {} connectionInfo {}",
connection.getMetaData().getURL(),
connectionInfo.getPeerService());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public Connection getConnection() throws SQLException {
return new TracingConnection(
delegate.getConnection(), connectionInfo, true, null, GlobalTracer.get());
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return new TracingConnection(
delegate.getConnection(username, password), connectionInfo, true, null, GlobalTracer.get());
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return delegate.unwrap(iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return delegate.isWrapperFor(iface);
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return delegate.getLogWriter();
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
delegate.setLogWriter(out);
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
delegate.setLoginTimeout(seconds);
}
@Override
public int getLoginTimeout() throws SQLException {
return delegate.getLoginTimeout();
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return delegate.getParentLogger();
}
public static DataSource wrapWithTracingIfEnabled(DataSource dataSource) {
return Boolean.valueOf(System.getenv("CHE_DB_TRACING_ENABLED"))
? new TracingDataSource(dataSource)
: dataSource;
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.cascade;
/**
* Context that is used only for sharing the state of the cascading operation among subscribers.
*
* @author Anton Korneta
* @author Sergii Leshchenko
*/
public class CascadeContext {
private Exception cause;
/** Returns the cause which has changed the state of the context. */
public Exception getCause() {
return cause;
}
/** Returns the state of the context. */
public boolean isFailed() {
return cause != null;
}
/** Sets the context into failed state. */
public CascadeContext fail(Exception cause) {
this.cause = cause;
return this;
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.cascade;
import org.eclipse.che.api.core.notification.EventSubscriber;
import org.eclipse.che.core.db.cascade.event.CascadeEvent;
/**
* Receives events and puts exceptions in the context to perform rollback operation if it is
* necessary.
*
* @author Anton Korneta
* @author Sergii Leschenko
*/
public abstract class CascadeEventSubscriber<T extends CascadeEvent> implements EventSubscriber<T> {
@Override
public void onEvent(T event) {
if (!event.getContext().isFailed()) {
try {
onCascadeEvent(event);
} catch (Exception ex) {
event.getContext().fail(ex);
}
}
}
/**
* Receives notification about cascade event.
*
* <p>If the method throws an exception it will be set to context to break event publishing and
* rethrow exception. Event is responsible for rethrowing or wrapping original exception.
*
* @see CascadeEvent#propagateException()
*/
public abstract void onCascadeEvent(T event) throws Exception;
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.cascade.event;
import org.eclipse.che.core.db.cascade.CascadeContext;
/**
* Special event type which is needed only for notification in the process which can require cascade
* operation.
*
* <p>Publisher should invoke {@link #propagateException()} to get cause of event canceling.
*
* <p>Rollback of operation must be performed when subscriber throws {@link Exception} during event
* processing.
*
* <p>Usage example:
*
* <pre>
* EventService bus = new EventService();
* bus.subscribe(new CascadeEventSubscriber&lt;MyEvent&gt;() {
* &#64;Override
* public void onCascadeEvent(MyEvent event) throws Exception {
* if (event.getEntityName().startsWith("reserved")) {
* throw new ConflictException("Entity name can't start with `reserved`.");
* }
* }
* });
* bus.publish(new MyEvent(...)).propagateException();
* </pre>
*
* @author Anton Korneta
* @author Sergii Leschenko
*/
public abstract class CascadeEvent {
protected final CascadeContext context = new CascadeContext();
public CascadeContext getContext() {
return context;
}
/**
* Propagates exception if subscriber throws it while event processing otherwise do nothing
*
* @throws Exception when any subscriber throws {@link Exception}
*/
public void propagateException() throws Exception {
if (context.isFailed()) {
throw context.getCause();
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.cascade.event;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ServerException;
/**
* Cascade event about an entity persisting.
*
* <p>{@link ConflictException} or {@link ServerException} can be rethrown during exception
* propagating.
*
* @author Sergii Leschenko
*/
public abstract class PersistEvent extends CascadeEvent {
@Override
public void propagateException() throws ConflictException, ServerException {
if (context.isFailed()) {
try {
throw context.getCause();
} catch (ConflictException | ServerException e) {
throw e;
} catch (Exception e) {
throw new ServerException(e.getLocalizedMessage(), e);
}
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.cascade.event;
import org.eclipse.che.api.core.ServerException;
/**
* Cascade event about an entity removing.
*
* <p>{@link ServerException} can be rethrown during exception propagating.
*
* @author Sergii Leschenko
*/
public abstract class RemoveEvent extends CascadeEvent {
@Override
public void propagateException() throws ServerException {
if (context.isFailed()) {
try {
throw context.getCause();
} catch (ServerException e) {
throw e;
} catch (Exception e) {
throw new ServerException(e.getLocalizedMessage(), e);
}
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.cascade.event;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
/**
* Cascade event about an entity updating.
*
* <p>{@link NotFoundException}, {@link ConflictException} or {@link ServerException} can be
* rethrown during exception propagating.
*
* @author Sergii Leschenko
*/
public abstract class UpdateEvent extends CascadeEvent {
@Override
public void propagateException() throws NotFoundException, ConflictException, ServerException {
if (context.isFailed()) {
try {
throw context.getCause();
} catch (NotFoundException | ConflictException | ServerException e) {
throw e;
} catch (Exception e) {
throw new ServerException(e.getLocalizedMessage(), e);
}
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.jpa;
import javax.persistence.RollbackException;
import org.eclipse.che.core.db.DBErrorCode;
/**
* Extends the standard {@link RollbackException} with an error code from {@link DBErrorCode}.
*
* @author Yevhenii Voevodin
*/
public class DetailedRollbackException extends RollbackException {
private DBErrorCode code;
public DetailedRollbackException(String message, Throwable cause, DBErrorCode code) {
super(message, cause);
this.code = code;
}
public DBErrorCode getCode() {
return code;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.jpa;
import org.eclipse.che.core.db.DBErrorCode;
/**
* Thrown when data couldn't be updated/stored due to unique constrain violation.
*
* @author Yevhenii Voevodin
* @see DBErrorCode#DUPLICATE_KEY
*/
public class DuplicateKeyException extends DetailedRollbackException {
public DuplicateKeyException(String message, Throwable cause) {
super(message, cause, DBErrorCode.DUPLICATE_KEY);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.jpa;
import org.eclipse.che.core.db.DBErrorCode;
/**
* Throws during inserts/updates entity that restricted by referential integrity and given
* insert/update refers to non-existing entity.
*
* @author Anton Korneta
* @see DBErrorCode#INTEGRITY_CONSTRAINT_VIOLATION
*/
public class IntegrityConstraintViolationException extends DetailedRollbackException {
public IntegrityConstraintViolationException(String message, Throwable cause) {
super(message, cause, DBErrorCode.INTEGRITY_CONSTRAINT_VIOLATION);
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.jpa;
import com.google.inject.ImplementedBy;
import org.eclipse.che.core.db.jpa.guice.GuiceJpaInitializer;
/**
* Initializes jpa components.
*
* @author Yevhenii Voevodin
*/
@ImplementedBy(GuiceJpaInitializer.class)
public interface JpaInitializer {
/** Initialized jpa components. */
void init();
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.jpa.eclipselink;
import com.google.inject.Inject;
import com.google.inject.Injector;
import javax.naming.NamingException;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.cdi.InjectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Allows to use dependency injection in entity listeners.
*
* <p>Example:
*
* <pre>
* class WorkspaceEntityListener {
*
* &#064;Inject EventBus bus; <- EventBus will be injected by Guice
*
* &#064;PreRemove
* public void preRemove(Workspace workspace) {
* bus.post(new BeforeWorkspaceRemovedEvent(workspace));
* }
* }
*
* &#064;Entity
* &#064;EntityListeners(WorkspaceEntityListener.class)
* class Workspace {
* // ...
* }
* </pre>
*
* @author Yevhenii Voevodin
*/
public class GuiceEntityListenerInjectionManager implements InjectionManager {
private static final Logger LOG =
LoggerFactory.getLogger(GuiceEntityListenerInjectionManager.class);
@Inject private Injector injector;
@Override
public Object createManagedBeanAndInjectDependencies(Class entityListenerClass)
throws NamingException {
try {
return injector.getInstance(entityListenerClass);
} catch (RuntimeException x) {
LOG.error(x.getLocalizedMessage(), x);
throw new NamingException(x.getLocalizedMessage());
}
}
@Override
public void cleanUp(AbstractSession session) {
// EntityListener objects are managed by Guice, nothing to cleanup
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.jpa.guice;
import com.google.inject.persist.PersistService;
import javax.inject.Inject;
import org.eclipse.che.core.db.jpa.JpaInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Should be bound as eager singleton. See <a
* href="https://github.com/google/guice/wiki/JPA">doc</a>
*
* @author Yevhenii Voevodin
* @author Anton Korneta
*/
public class GuiceJpaInitializer implements JpaInitializer {
private static final Logger LOG = LoggerFactory.getLogger(GuiceJpaInitializer.class);
@Inject private PersistService persistService;
public void init() {
try {
persistService.start();
} catch (Exception x) {
LOG.error(x.getLocalizedMessage(), x);
}
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.schema;
/**
* Thrown when any schema initialization/migration problem occurs.
*
* @author Yevhenii Voevodin
*/
public class SchemaInitializationException extends Exception {
public SchemaInitializationException(String message, Throwable cause) {
super(message, cause);
}
public SchemaInitializationException(String message) {
super(message);
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.schema;
import java.util.Map;
/**
* Initializes database schema or migrates an old version of it to a new one.
*
* @author Yevhenii Voevodin
*/
public interface SchemaInitializer {
/**
* Initializes database schema or migrates an old schema to a new one.
*
* @return initialization properties
* @throws SchemaInitializationException thrown when any error occurs during schema
* initialization/migration
*/
Map<String, String> init() throws SchemaInitializationException;
}

View File

@ -0,0 +1,210 @@
/*
* Copyright (c) 2012-2021 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 org.eclipse.che.core.db.schema.impl.flyway;
import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.MigrationType;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.api.resolver.BaseMigrationResolver;
import org.flywaydb.core.api.resolver.ResolvedMigration;
import org.flywaydb.core.internal.dbsupport.DbSupport;
import org.flywaydb.core.internal.resolver.ResolvedMigrationImpl;
import org.flywaydb.core.internal.resolver.sql.SqlMigrationExecutor;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.PlaceholderReplacer;
import org.flywaydb.core.internal.util.scanner.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Resolves SQL migrations from the configured locations, allows overriding of default scripts with
* vendor specific ones.
*
* <ul>
* Migration scripts must follow the next rules:
* <li>It must be placed in the project dir directory e.g. <i>5.0.1</i>
* <li>Project dir directory must be placed in dedicated directory e.g. <i>resources/sql</i>
* <li>Migration/Initialization script name must start with a number e.g <i>1.init.sql</i>, this
* number indicates the subversion of the database migration, e.g. for dir <i>5.0.0</i> and
* migration script <i>1.init.sql</i> database migration dir will be <i>5.0.0.1</i>
* <li>If a file is not a part of migration it shouldn't end with migration prefix e.g.
* <i>.sql</i> then resolver will ignore it
* </ul>
*
* <p>For the structure:
*
* <pre>
* resources/
* sql/
* 5.0.0/
* 1.init.sql
* 5.0.0-M1/
* 1.rename_fields.sql
* 2.add_workspace_constraint.sql
* postgresql/
* 2.add_workspace_constraint.sql
* 5.0.1/
* 1.stacks_migration.sql
* </pre>
*
* And configuration:
*
* <pre>
* prefix - ""
* suffix - ".sql"
* separator - "."
* locations - "classpath:sql"
* </pre>
*
* <ul>
* 4 database migrations will be resolved
* <li>5.0.0.1 - initialization script based on file <i>sql/5.0.0/1.init.sql</i>
* <li>5.0.0.1.1 - modification script based on file <i>sql/5.0.0-M1/1.rename_fields.sql</i>
* <li>5.0.0.1.2 - modification script(if postgresql is current provider) based on file
* <i>sql/5.0.0-M1/postgresql/2.add_workspace_constraint.sql</i>
* <li>5.0.1.1 - modification script based on file <i>sql/5.0.1/1.stacks_migrations.sql</i>
* </ul>
*
* <p>It is also possible to configure several locations then all of those locations will be
* analyzed for migration scripts existence. For example:
*
* <p>For the structure:
*
* <pre>
* che/
* resources/
* che-schema/
* 5.0.0/
* 1.init.sql
* another-project/
* resources/
* custom-schema/
* 5.0.0/
* 2.init_additional_tables.sql
* </pre>
*
* And configuration:
*
* <pre>
* prefix - ""
* suffix - ".sql"
* separator - "."
* locations - "classpath:che-schema, classpath:custom-schema"
* </pre>
*
* <ul>
* 2 database migrations will be resolved
* <li>5.0.0.1 - initialization script based on file <i>che-schema/5.0.0/1.init.sql</i>
* <li>5.0.0.2 - modification script based on file
* <i>custom-schema/5.0.0/2.init_additional_tables.sql</i>
* </ul>
*
* @author Yevhenii Voevodin
*/
public class CustomSqlMigrationResolver extends BaseMigrationResolver {
private static final Logger LOG = LoggerFactory.getLogger(CustomSqlMigrationResolver.class);
private final String vendorName;
private final ResourcesFinder finder;
private final VersionResolver versionResolver;
private final SqlScriptCreator scriptsCreator;
private final DbSupport dbSupport;
private final PlaceholderReplacer placeholderReplacer;
public CustomSqlMigrationResolver(
String dbProviderName, DbSupport dbSupport, PlaceholderReplacer placeholderReplacer) {
this.vendorName = dbProviderName;
this.dbSupport = dbSupport;
this.placeholderReplacer = placeholderReplacer;
this.finder = new ResourcesFinder();
this.versionResolver = new VersionResolver();
this.scriptsCreator = new SqlScriptCreator();
}
@Override
public Collection<ResolvedMigration> resolveMigrations() {
try {
return resolveSqlMigrations();
} catch (IOException | SQLException x) {
throw new RuntimeException(x.getLocalizedMessage(), x);
}
}
private List<ResolvedMigration> resolveSqlMigrations() throws IOException, SQLException {
LOG.info(
"Searching for SQL scripts in locations {}",
Arrays.toString(flywayConfiguration.getLocations()));
final Map<Location, List<Resource>> allResources = finder.findResources(flywayConfiguration);
LOG.debug("Found scripts: {}", allResources);
final Map<String, Map<String, SqlScript>> scriptsInDir = new HashMap<>();
for (Location location : allResources.keySet()) {
final List<Resource> resources = allResources.get(location);
for (Resource resource : resources) {
final SqlScript newScript = scriptsCreator.createScript(location, resource);
if (!scriptsInDir.containsKey(newScript.dir)) {
scriptsInDir.put(newScript.dir, new HashMap<>(4));
}
final Map<String, SqlScript> existingScripts = scriptsInDir.get(newScript.dir);
final SqlScript existingScript = existingScripts.get(newScript.name);
if (existingScript == null) {
existingScripts.put(newScript.name, newScript);
} else if (Objects.equals(existingScript.vendor, newScript.vendor)) {
throw new FlywayException(
format(
"More than one script with name '%s' is registered for "
+ "database vendor '%s', script '%s' conflicts with '%s'",
newScript.name, existingScript.vendor, newScript, existingScript));
} else if (vendorName.equals(newScript.vendor)) {
existingScripts.put(newScript.name, newScript);
}
}
}
final Map<MigrationVersion, ResolvedMigration> migrations = new HashMap<>();
for (SqlScript script :
scriptsInDir.values().stream()
.flatMap(scripts -> scripts.values().stream())
.collect(toList())) {
final ResolvedMigrationImpl migration = new ResolvedMigrationImpl();
migration.setVersion(versionResolver.resolve(script, flywayConfiguration));
migration.setScript(script.resource.getLocation());
migration.setPhysicalLocation(script.resource.getLocationOnDisk());
migration.setType(MigrationType.SQL);
migration.setDescription(script.name);
migration.setChecksum(
ByteSource.wrap(script.resource.loadAsBytes()).hash(Hashing.crc32()).asInt());
migration.setExecutor(
new SqlMigrationExecutor(
dbSupport, script.resource, placeholderReplacer, flywayConfiguration));
if (migrations.put(migration.getVersion(), migration) != null) {
throw new FlywayException("Two migrations with the same version detected");
}
}
return new ArrayList<>(migrations.values());
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (c) 2012-2021 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 org.eclipse.che.core.db.schema.impl.flyway;
import static org.eclipse.che.core.db.DBInitializer.BARE_DB_INIT_PROPERTY_NAME;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.sql.DataSource;
import org.eclipse.che.core.db.schema.SchemaInitializationException;
import org.eclipse.che.core.db.schema.SchemaInitializer;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.internal.dbsupport.DbSupport;
import org.flywaydb.core.internal.dbsupport.DbSupportFactory;
import org.flywaydb.core.internal.metadatatable.MetaDataTable;
import org.flywaydb.core.internal.metadatatable.MetaDataTableImpl;
import org.flywaydb.core.internal.util.PlaceholderReplacer;
/**
* <a href="https://flywaydb.org/">Flyway</a> based schema initializer.
*
* @author Yevhenii Voevodin
*/
public class FlywaySchemaInitializer implements SchemaInitializer {
private final DataSource dataSource;
private final String[] locations;
private final String scriptsPrefix;
private final String scriptsSuffix;
private final String versionSeparator;
private final boolean baselineOnMigrate;
private final String baselineVersion;
private final PlaceholderReplacer placeholderReplacer;
/**
* Creates a new instance of flyway schema initializer.
*
* @param scriptsLocations the locations where to search migration scripts, if locations is not
* prefixed or prefixed with <i>classpath:</i> then initializer will try to find scripts in
* classpath using {@code Thread.currentThread().}{@link Thread#getContextClassLoader()
* getContextClassLoader()}
* @param scriptsPrefix prefix of migration scripts e.g. 'v' or empty string
* @param scriptsSuffix suffix of migration scripts e.g. '.sql'
* @param versionSeparator separate version from the other part of script name e.g. '.' or '__'
* @param baselineOnMigrate whether to ignore scripts up to the version configured by {@code
* baselineVersion}
* @param baselineVersion up to this version all the scripts ignored, unless schema is initialized
* first time, note that scripts with version equal to baseline version are also ignored
* @param dataSource data source used for migrations
* @param placeholderReplacer used to replace variables in script with configured values
*/
@Inject
public FlywaySchemaInitializer(
@Named("db.schema.flyway.scripts.locations") String[] scriptsLocations,
@Named("db.schema.flyway.scripts.prefix") String scriptsPrefix,
@Named("db.schema.flyway.scripts.suffix") String scriptsSuffix,
@Named("db.schema.flyway.scripts.version_separator") String versionSeparator,
@Named("db.schema.flyway.baseline.enabled") boolean baselineOnMigrate,
@Named("db.schema.flyway.baseline.version") String baselineVersion,
DataSource dataSource,
PlaceholderReplacer placeholderReplacer) {
this.dataSource = dataSource;
this.locations = scriptsLocations;
this.scriptsPrefix = scriptsPrefix;
this.scriptsSuffix = scriptsSuffix;
this.versionSeparator = versionSeparator;
this.baselineOnMigrate = baselineOnMigrate;
this.baselineVersion = baselineVersion;
this.placeholderReplacer = placeholderReplacer;
}
/** Creates a new flyway based initializer with default values. */
public FlywaySchemaInitializer(DataSource dataSource, String... locations) {
this(locations, "", ".sql", "__", false, "", dataSource, PlaceholderReplacer.NO_PLACEHOLDERS);
}
@Override
public Map<String, String> init() throws SchemaInitializationException {
final Map<String, String> initResult = new HashMap<>();
try (final Connection conn = dataSource.getConnection()) {
final Flyway flyway = new Flyway();
flyway.setDataSource(dataSource);
flyway.setLocations(locations);
flyway.setClassLoader(Thread.currentThread().getContextClassLoader());
final DbSupport dbSupport = DbSupportFactory.createDbSupport(conn, true);
final MetaDataTable mt =
new MetaDataTableImpl(
dbSupport,
dbSupport.getOriginalSchema().getTable(flyway.getTable()),
flyway.getInstalledBy());
initResult.put(BARE_DB_INIT_PROPERTY_NAME, String.valueOf(!mt.hasAppliedMigrations()));
final String productName = conn.getMetaData().getDatabaseProductName().toLowerCase();
flyway.setResolvers(
new CustomSqlMigrationResolver(productName, dbSupport, placeholderReplacer));
flyway.setSkipDefaultResolvers(true);
flyway.setBaselineOnMigrate(baselineOnMigrate);
if (baselineOnMigrate) {
flyway.setBaselineVersionAsString(baselineVersion);
}
flyway.setSqlMigrationSeparator(versionSeparator);
flyway.setSqlMigrationSuffix(scriptsSuffix);
flyway.setSqlMigrationPrefix(scriptsPrefix);
flyway.migrate();
} catch (SQLException | RuntimeException x) {
throw new SchemaInitializationException(x.getLocalizedMessage(), x);
}
return initResult;
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.schema.impl.flyway;
import javax.inject.Inject;
import javax.inject.Provider;
import org.eclipse.che.inject.ConfigurationProperties;
import org.flywaydb.core.internal.util.PlaceholderReplacer;
/**
* Placeholder replacer that uses configuration properties.
*
* @author Yevhenii Voevodin
*/
public class PlaceholderReplacerProvider implements Provider<PlaceholderReplacer> {
private final PlaceholderReplacer replacer;
@Inject
public PlaceholderReplacerProvider(ConfigurationProperties properties) {
replacer = new PlaceholderReplacer(properties.getProperties(".*"), "${", "}");
}
@Override
public PlaceholderReplacer get() {
return replacer;
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.schema.impl.flyway;
import static com.google.common.collect.Lists.newArrayList;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.flywaydb.core.api.configuration.FlywayConfiguration;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.scanner.Resource;
import org.flywaydb.core.internal.util.scanner.classpath.ClassPathScanner;
import org.flywaydb.core.internal.util.scanner.filesystem.FileSystemScanner;
/**
* Searches for sql scripts in given places.
*
* @author Yevhenii Voevodin
*/
class ResourcesFinder {
/**
* Finds script resources in configured {@link FlywayConfiguration#getLocations()}.
*
* @param configuration flyway configuration to find scripts
* @return found scripts or an empty list if nothing found
* @throws IOException when any io error occurs during scripts look up
*/
Map<Location, List<Resource>> findResources(FlywayConfiguration configuration)
throws IOException {
final String prefix = configuration.getSqlMigrationPrefix();
final String suffix = configuration.getSqlMigrationSuffix();
final ClassPathScanner cpScanner = new ClassPathScanner(configuration.getClassLoader());
final FileSystemScanner fsScanner = new FileSystemScanner();
final Map<Location, List<Resource>> resources = new HashMap<>();
for (String rawLocation : configuration.getLocations()) {
final Location location = new Location(rawLocation);
if (location.isClassPath()) {
resources.put(location, newArrayList(cpScanner.scanForResources(location, prefix, suffix)));
} else {
resources.put(location, newArrayList(fsScanner.scanForResources(location, prefix, suffix)));
}
}
return resources;
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.schema.impl.flyway;
import java.util.Objects;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.scanner.Resource;
/**
* Data object for holding information about sql script.
*
* @author Yevhenii Voevodin
*/
class SqlScript {
final Resource resource;
final Location location;
final String dir;
final String vendor;
final String name;
SqlScript(Resource resource, Location location, String dir, String vendor, String name) {
this.resource = resource;
this.location = location;
this.name = name;
this.vendor = vendor;
this.dir = dir;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SqlScript)) {
return false;
}
final SqlScript that = (SqlScript) obj;
return Objects.equals(resource, that.resource)
&& Objects.equals(location, that.location)
&& Objects.equals(dir, that.dir)
&& Objects.equals(vendor, that.vendor)
&& Objects.equals(name, that.name);
}
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + Objects.hashCode(resource);
hash = 31 * hash + Objects.hashCode(location);
hash = 31 * hash + Objects.hashCode(dir);
hash = 31 * hash + Objects.hashCode(vendor);
hash = 31 * hash + Objects.hashCode(name);
return hash;
}
@Override
public String toString() {
return "SqlScript{"
+ "resource="
+ resource
+ ", location="
+ location
+ ", dir='"
+ dir
+ '\''
+ ", vendor='"
+ vendor
+ '\''
+ ", name='"
+ name
+ '\''
+ '}';
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.schema.impl.flyway;
import static java.lang.String.format;
import java.io.File;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.scanner.Resource;
/**
* Creates new {@link SqlScript} instance from given resource.
*
* @author Yevhenii Voevodin
*/
class SqlScriptCreator {
/**
* Create a new instance of script based on location and resource.
*
* @param location root location of the given resource
* @param resource script resource
* @return a new instance of sql script based on location and resource
* @throws FlywayException when script can't be created from the resource
*/
SqlScript createScript(Location location, Resource resource) {
final String separator = location.isClassPath() ? "/" : File.separator;
// '/root-location/5.0.0-M7/v1__init.sql' -> '5.0.0-M7/v1__init.sql'
final String relLocation = resource.getLocation().substring(location.getPath().length() + 1);
final String[] paths = relLocation.split(separator);
// 5.0.0-M1/v1__init.sql
if (paths.length == 2) {
return new SqlScript(resource, location, paths[0], null, paths[1]);
}
// 5.0.0-M1/postgresql/v1__init.sql
if (paths.length == 3) {
return new SqlScript(resource, location, paths[0], paths[1], paths[2]);
}
throw new FlywayException(
format(
"Sql script location must be either in 'location-root/version-dir' "
+ "or in 'location-root/version-dir/provider-name', but script '%s' is not in root '%s'",
resource.getLocation(), location.getPath()));
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.schema.impl.flyway;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.api.configuration.FlywayConfiguration;
/**
* Creates versions for scripts depending on the provided data.
*
* <ul>
* A few examples:
* <li>5.0.0-M7/v1__init.sql => 5.0.0.7.1
* <li>5.0.0-M8/v2.1__modify.sql => 5.0.0.8.2.1
* </ul>
*
* @author Yevhenii Voevodin
*/
class VersionResolver {
private static final Pattern NOT_VERSION_CHARS_PATTERN = Pattern.compile("[^0-9.]");
private final Map<String, String> normalizedDirs = new HashMap<>();
/**
* Creates migration version based on script data.
*
* @param script script for which to resolve the version
* @param configuration flyway configuration used for resolution parameters
*/
MigrationVersion resolve(SqlScript script, FlywayConfiguration configuration) {
String normalizedDir = normalizedDirs.get(script.dir);
if (normalizedDir == null) {
// 5.0.0-M1 -> 5.0.0.M1 -> 5.0.0.1
normalizedDir =
NOT_VERSION_CHARS_PATTERN.matcher(script.dir.replace("-", ".")).replaceAll("");
normalizedDirs.put(script.dir, normalizedDir);
}
// separate version from the other part of the name
final int sepIdx = script.name.indexOf(configuration.getSqlMigrationSeparator());
if (sepIdx == -1) {
throw new FlywayException(
format(
"sql script name '%s' is not valid, name must contain '%s'",
script.name, configuration.getSqlMigrationSeparator()));
}
// check whether part before separator is not empty
String version = script.name.substring(0, sepIdx);
if (version.isEmpty()) {
throw new FlywayException(
format(
"sql script name '%s' is not valid, name must provide version like "
+ "'%s4%smigration_description.sql",
configuration.getSqlMigrationPrefix(),
script.name,
configuration.getSqlMigrationSeparator()));
}
// extract sql script version without prefix
final String prefix = configuration.getSqlMigrationPrefix();
if (!isNullOrEmpty(prefix) && script.name.startsWith(prefix)) {
version = version.substring(prefix.length());
}
return MigrationVersion.fromVersion(normalizedDir + '.' + version);
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) 2012-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 org.eclipse.che.core.db;
import static org.mockito.Mockito.lenient;
import static org.testng.Assert.assertEquals;
import com.google.common.collect.ImmutableMap;
import io.opentracing.contrib.jdbc.TracingConnection;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(value = {MockitoTestNGListener.class})
public class TracingDataSourceTest {
@Mock DataSource dataSource;
@Mock Connection connection;
@Mock DatabaseMetaData databaseMetaData;
@BeforeMethod
@AfterMethod
public void cleanup() throws Exception {
HashMap<String, String> newEnv = new HashMap<>(System.getenv());
newEnv.remove("CHE_DB_TRACING_ENABLED");
setEnv(newEnv);
lenient().when(dataSource.getConnection()).thenReturn(connection);
lenient().when(connection.getMetaData()).thenReturn(databaseMetaData);
}
@Test
public void shouldBeAbleToGetConnection() throws SQLException {
TracingDataSource ds = new TracingDataSource(dataSource);
Connection actual = ds.getConnection();
assertEquals(actual.getClass(), TracingConnection.class);
Mockito.verify(dataSource, Mockito.times(2)).getConnection();
}
@Test
public void shouldBeAbleToGetConnectionWithEmailAndPassword() throws SQLException {
TracingDataSource ds = new TracingDataSource(dataSource);
Connection actual = ds.getConnection("user", "password");
assertEquals(actual.getClass(), TracingConnection.class);
Mockito.verify(dataSource).getConnection(Mockito.eq("user"), Mockito.eq("password"));
}
@Test
public void shouldBeAbleTogetTracingDataSource() throws Exception {
setEnv(ImmutableMap.of("CHE_DB_TRACING_ENABLED", "true"));
DataSource actual = TracingDataSource.wrapWithTracingIfEnabled(dataSource);
assertEquals(actual.getClass(), TracingDataSource.class);
}
@Test
public void shouldNotWrapDatasourceIfEnvSetToFalse() throws Exception {
setEnv(ImmutableMap.of("CHE_DB_TRACING_ENABLED", "false"));
DataSource actual = TracingDataSource.wrapWithTracingIfEnabled(dataSource);
assertEquals(actual, dataSource);
}
@Test
public void shouldNotWrapDatasourceIfEnvIsNotSet() throws Exception {
DataSource actual = TracingDataSource.wrapWithTracingIfEnabled(dataSource);
assertEquals(actual, dataSource);
}
protected static void setEnv(Map<String, String> newenv) throws Exception {
try {
Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
theEnvironmentField.setAccessible(true);
Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
env.putAll(newenv);
Field theCaseInsensitiveEnvironmentField =
processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
theCaseInsensitiveEnvironmentField.setAccessible(true);
Map<String, String> cienv =
(Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
cienv.putAll(newenv);
} catch (NoSuchFieldException e) {
Class[] classes = Collections.class.getDeclaredClasses();
Map<String, String> env = System.getenv();
for (Class cl : classes) {
if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Object obj = field.get(env);
Map<String, String> map = (Map<String, String>) obj;
map.clear();
map.putAll(newenv);
}
}
}
}
}

View File

@ -0,0 +1,353 @@
/*
* Copyright (c) 2012-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 org.eclipse.che.core.db.schema.impl.flyway;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.io.StringReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.sql.DataSource;
import org.eclipse.che.commons.lang.IoUtil;
import org.eclipse.che.core.db.schema.SchemaInitializationException;
import org.eclipse.che.core.db.schema.SchemaInitializer;
import org.flywaydb.core.internal.util.PlaceholderReplacer;
import org.h2.jdbcx.JdbcDataSource;
import org.h2.tools.RunScript;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* Tests {@link FlywaySchemaInitializer}.
*
* @author Yevhenii Voevodin
*/
public class FlywaySchemaInitializerTest {
private static final String SCRIPTS_ROOT = "flyway/sql";
private JdbcDataSource dataSource;
@BeforeMethod
public void setUp() throws URISyntaxException {
dataSource = new JdbcDataSource();
dataSource.setUrl("jdbc:h2:mem:flyway_test;DB_CLOSE_DELAY=-1");
}
@AfterMethod
public void cleanup() throws SQLException, URISyntaxException {
try (Connection conn = dataSource.getConnection()) {
RunScript.execute(conn, new StringReader("SHUTDOWN"));
}
IoUtil.deleteRecursive(targetDir().resolve(Paths.get(SCRIPTS_ROOT)).toFile());
}
@Test
public void initializesSchemaWhenDatabaseIsEmpty() throws Exception {
createScript("1.0/1__init.sql", "CREATE TABLE test (id INT, text TEXT, PRIMARY KEY (id));");
createScript(
"1.0/2__add_data.sql",
"INSERT INTO test VALUES(1, 'test1');"
+ "INSERT INTO test VALUES(2, 'test2');"
+ "INSERT INTO test VALUES(3, 'test3');");
createScript("2.0/1__add_more_data.sql", "INSERT INTO test VALUES(4, 'test4');");
createScript(
"2.0/postgresql/1__add_more_data.sql", "INSERT INTO test VALUES(4, 'postgresql-data');");
final SchemaInitializer initializer = FlywayInitializerBuilder.from(dataSource).build();
initializer.init();
assertEquals(
queryEntities(),
Sets.newHashSet(
new TestEntity(1, "test1"),
new TestEntity(2, "test2"),
new TestEntity(3, "test3"),
new TestEntity(4, "test4")));
// second init must do nothing, so there are no conflicts
initializer.init();
}
@Test(expectedExceptions = SchemaInitializationException.class)
public void failsIfBaseLineIsNotConfiguredProperly() throws Exception {
execQuery(
"CREATE TABLE test (id INT, text TEXT, PRIMARY KEY (id));"
+ "INSERT INTO test VALUES(1, 'test1');"
+ "INSERT INTO test VALUES(2, 'test2');"
+ "INSERT INTO test VALUES(3, 'test3');");
createScript("1.0/1__init.sql", "CREATE TABLE test (id INT, text TEXT, PRIMARY KEY (id));");
FlywayInitializerBuilder.from(dataSource)
.setBaselineOnMigrate(true)
.setBaselineVersion("1.0")
.build()
.init();
}
@Test
public void executesOnlyThoseMigrationsWhichGoAfterBaseline() throws Exception {
createScript("1.0/1__init.sql", "CREATE TABLE test (id INT, text TEXT, PRIMARY KEY (id));");
createScript(
"2.0/1__add_data.sql",
"INSERT INTO test VALUES(1, 'test1');"
+ "INSERT INTO test VALUES(2, 'test2');"
+ "INSERT INTO test VALUES(3, 'test3');");
final FlywaySchemaInitializer initializer =
FlywayInitializerBuilder.from(dataSource)
.setBaselineOnMigrate(true)
.setBaselineVersion("1.0.1")
.build();
initializer.init();
assertEquals(
queryEntities(),
Sets.newHashSet(
new TestEntity(1, "test1"), new TestEntity(2, "test2"), new TestEntity(3, "test3")));
// second init must do nothing, so there are no conflicts
initializer.init();
}
@Test
public void initializesSchemaWhenDatabaseIsEmptyAndBaselineIsConfigured() throws Exception {
createScript("1.0/1__init.sql", "CREATE TABLE test (id INT, text TEXT, PRIMARY KEY (id));");
createScript(
"2.0/1__add_data.sql",
"INSERT INTO test VALUES(1, 'test1');"
+ "INSERT INTO test VALUES(2, 'test2');"
+ "INSERT INTO test VALUES(3, 'test3');");
final FlywaySchemaInitializer initializer =
FlywayInitializerBuilder.from(dataSource)
.setBaselineOnMigrate(true)
.setBaselineVersion("1.0.1")
.build();
initializer.init();
assertEquals(
queryEntities(),
Sets.newHashSet(
new TestEntity(1, "test1"), new TestEntity(2, "test2"), new TestEntity(3, "test3")));
// second init must do nothing, so there are no conflicts
initializer.init();
}
@Test
public void selectsProviderSpecificScriptsInPreferenceToDefaultOnes() throws Exception {
createScript("1.0/1__init.sql", "CREATE TABLE test (id INT, text TEXT, PRIMARY KEY (id));");
createScript("2.0/1__add_data.sql", "INSERT INTO test VALUES(1, 'default data');");
createScript("2.0/h2/1__add_data.sql", "INSERT INTO test VALUES(1, 'h2 data');");
final FlywaySchemaInitializer initializer = FlywayInitializerBuilder.from(dataSource).build();
initializer.init();
assertEquals(queryEntities(), Sets.newHashSet(new TestEntity(1, "h2 data")));
// second init must do nothing, so there are no conflicts
initializer.init();
}
@Test
public void replacesVariablesWhenPlaceholderReplacerIsConfigured() throws Exception {
createScript(
"1.0/1__init.sql",
"CREATE TABLE test (id INT, text TEXT, PRIMARY KEY (id));"
+ "INSERT INTO test VALUES(1, '${variable}');");
FlywayInitializerBuilder.from(dataSource)
.setReplacer(new PlaceholderReplacer(ImmutableMap.of("variable", "test"), "${", "}"))
.build()
.init();
assertEquals(queryEntities(), Sets.newHashSet(new TestEntity(1, "test")));
}
private Set<TestEntity> queryEntities() throws SQLException {
final Set<TestEntity> entities = new HashSet<>();
try (Connection conn = dataSource.getConnection()) {
final ResultSet result = RunScript.execute(conn, new StringReader("SELECT * FROM test"));
while (result.next()) {
entities.add(new TestEntity(result.getLong("id"), result.getString("text")));
}
}
return entities;
}
private ResultSet execQuery(String query) throws SQLException {
try (Connection conn = dataSource.getConnection()) {
return RunScript.execute(conn, new StringReader(query));
}
}
private static Path createScript(String relativePath, String content)
throws URISyntaxException, IOException {
return createFile(
targetDir().resolve(Paths.get(SCRIPTS_ROOT)).resolve(relativePath).toString(), content);
}
private static Path createFile(String filepath, String content)
throws URISyntaxException, IOException {
final Path path = targetDir().resolve(Paths.get(filepath));
if (!Files.exists(path.getParent())) {
Files.createDirectories(path.getParent());
}
Files.write(path, content.getBytes(StandardCharsets.UTF_8));
return path;
}
private static Path targetDir() throws URISyntaxException {
final URL url = Thread.currentThread().getContextClassLoader().getResource(".");
assertNotNull(url);
return Paths.get(url.toURI()).getParent();
}
private static class TestEntity {
final long id;
final String text;
private TestEntity(long id, String text) {
this.id = id;
this.text = text;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TestEntity)) {
return false;
}
final TestEntity that = (TestEntity) obj;
return id == that.id && Objects.equals(text, that.text);
}
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + Long.hashCode(id);
hash = 31 * hash + Objects.hashCode(text);
return hash;
}
@Override
public String toString() {
return "TestEntity{" + "id=" + id + ", text='" + text + '\'' + '}';
}
}
private static class FlywayInitializerBuilder {
public static FlywayInitializerBuilder from(DataSource dataSource) {
try {
final String scriptsRoot = targetDir().resolve(Paths.get(SCRIPTS_ROOT)).toString();
return new FlywayInitializerBuilder()
.setDataSource(dataSource)
.setScriptsPrefix("")
.setScriptsSuffix(".sql")
.setVersionSeparator("__")
.setReplacer(PlaceholderReplacer.NO_PLACEHOLDERS)
.setBaselineOnMigrate(false)
.addLocation("filesystem:" + scriptsRoot);
} catch (Exception x) {
throw new RuntimeException(x.getMessage(), x);
}
}
private DataSource dataSource;
private List<String> locations;
private String scriptsPrefix;
private String scriptsSuffix;
private String versionSeparator;
private boolean baselineOnMigrate;
private String baselineVersion;
private PlaceholderReplacer replacer;
public FlywayInitializerBuilder setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
return this;
}
public FlywayInitializerBuilder setReplacer(PlaceholderReplacer replacer) {
this.replacer = replacer;
return this;
}
public FlywayInitializerBuilder addLocation(String location) {
if (locations == null) {
locations = new ArrayList<>();
}
locations.add(location);
return this;
}
public FlywayInitializerBuilder setScriptsPrefix(String scriptsPrefix) {
this.scriptsPrefix = scriptsPrefix;
return this;
}
public FlywayInitializerBuilder setScriptsSuffix(String scriptsSuffix) {
this.scriptsSuffix = scriptsSuffix;
return this;
}
public FlywayInitializerBuilder setVersionSeparator(String versionSeparator) {
this.versionSeparator = versionSeparator;
return this;
}
public FlywayInitializerBuilder setBaselineOnMigrate(boolean baselineOnMigrate) {
this.baselineOnMigrate = baselineOnMigrate;
return this;
}
public FlywayInitializerBuilder setBaselineVersion(String baselineVersion) {
this.baselineVersion = baselineVersion;
return this;
}
public FlywaySchemaInitializer build() {
if (locations == null) {
throw new IllegalStateException("locations required");
}
return new FlywaySchemaInitializer(
locations.toArray(new String[locations.size()]),
scriptsPrefix,
scriptsSuffix,
versionSeparator,
baselineOnMigrate,
baselineVersion,
dataSource,
replacer);
}
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.schema.impl.flyway;
import static com.google.common.collect.Sets.newHashSet;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.configuration.FlywayConfiguration;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.scanner.Resource;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* Tests {@link ResourcesFinder}.
*
* @author Yevhenii Voevodin
*/
public class ResourcesFinderTest {
private final List<Path> cleanAfter = new ArrayList<>();
private final Flyway flyway = new Flyway();
@BeforeMethod
public void setUpDefaults() {
flyway.setSqlMigrationSuffix(".sql");
flyway.setSqlMigrationPrefix("");
}
@AfterMethod
public void cleanup() throws IOException {
for (Path path : cleanAfter) {
Files.delete(path);
}
cleanAfter.clear();
}
@Test
public void findsScriptsInClassPath() throws Exception {
flyway.setLocations("classpath:finder-sql-files");
cleanAfter.addAll(
createFiles(
"finder-sql-files/1.0/1.sql",
"finder-sql-files/1.0/2.sql",
"finder-sql-files/2.0/1.sql",
"finder-sql-files/2.0/postgresql/1.sql"));
final Set<String> locations = findResources(flyway).get("classpath:finder-sql-files");
assertEquals(
locations,
newHashSet(
"finder-sql-files/1.0/1.sql",
"finder-sql-files/1.0/2.sql",
"finder-sql-files/2.0/1.sql",
"finder-sql-files/2.0/postgresql/1.sql"));
}
@Test
public void findsScriptsOnFileSystem() throws Exception {
final List<Path> paths =
createFiles(
"finder-sql-files/1.0/1.sql",
"finder-sql-files/1.0/2.sql",
"finder-sql-files/2.0/1.sql",
"finder-sql-files/2.0/postgresql/1.sql");
cleanAfter.addAll(paths);
final Path finderSqlFiles = paths.get(0).getParent().getParent();
final String fsLocation = "filesystem:" + finderSqlFiles.toAbsolutePath();
flyway.setLocations(fsLocation);
final Set<String> locations = findResources(flyway).get(fsLocation);
assertEquals(
locations,
newHashSet(
finderSqlFiles.resolve("1.0").resolve("1.sql").toString(),
finderSqlFiles.resolve("1.0").resolve("2.sql").toString(),
finderSqlFiles.resolve("2.0").resolve("1.sql").toString(),
finderSqlFiles.resolve("2.0").resolve("postgresql").resolve("1.sql").toString()));
}
@Test
public void findsFileSystemAndClassPathScripts() throws Exception {
final List<Path> paths =
createFiles(
"finder-fs-sql-files/1.0/1.sql",
"finder-fs-sql-files/2.0/2.sql",
"finder-cp-sql-files/1.0/2.sql",
"finder-cp-sql-files/2.0/postgresql/1.sql");
cleanAfter.addAll(paths);
final Path finderFsSqlFiles = paths.get(0).getParent().getParent();
final String fsLocation = "filesystem:" + finderFsSqlFiles.toAbsolutePath();
final String cpLocation = "classpath:finder-cp-sql-files";
flyway.setLocations(fsLocation, cpLocation);
final Map<String, Set<String>> locations = findResources(flyway);
assertEquals(
locations.get(fsLocation),
newHashSet(
finderFsSqlFiles.resolve("1.0").resolve("1.sql").toString(),
finderFsSqlFiles.resolve("2.0").resolve("2.sql").toString()));
assertEquals(
locations.get(cpLocation),
newHashSet("finder-cp-sql-files/1.0/2.sql", "finder-cp-sql-files/2.0/postgresql/1.sql"));
}
private static Map<String, Set<String>> findResources(FlywayConfiguration configuration)
throws IOException {
final Map<Location, List<Resource>> resources =
new ResourcesFinder().findResources(configuration);
final Map<String, Set<String>> locations = new HashMap<>();
for (Map.Entry<Location, List<Resource>> entry : resources.entrySet()) {
locations.put(
entry.getKey().toString(),
entry.getValue().stream().map(Resource::getLocation).collect(Collectors.toSet()));
}
return locations;
}
private static List<Path> createFiles(String... paths) throws URISyntaxException, IOException {
final URL url = Thread.currentThread().getContextClassLoader().getResource(".");
assertNotNull(url);
final Path classesDir = Paths.get(url.toURI());
final List<Path> createdFiles = new ArrayList<>(paths.length);
for (String stringPath : paths) {
final Path path = classesDir.resolve(Paths.get(stringPath));
if (!Files.exists(path.getParent())) {
Files.createDirectories(path.getParent());
}
Files.write(path, path.toString().getBytes(StandardCharsets.UTF_8));
createdFiles.add(path);
}
return createdFiles;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.schema.impl.flyway;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.scanner.Resource;
import org.flywaydb.core.internal.util.scanner.filesystem.FileSystemResource;
import org.testng.annotations.Test;
/**
* Tests {@link SqlScriptCreator}.
*
* @author Yevhenii Voevodin
*/
public class SqlScriptCreatorTest {
@Test
public void createsScript() throws Exception {
final Location location = new Location("filesystem:schema");
final Resource resource = new FileSystemResource("schema/5.0.0-M7/v1__init.sql");
final SqlScriptCreator scriptsCreator = new SqlScriptCreator();
final SqlScript script = scriptsCreator.createScript(location, resource);
assertEquals(script.name, "v1__init.sql");
assertEquals(script.location, location);
assertEquals(script.dir, "5.0.0-M7");
assertEquals(script.resource.getLocation(), resource.getLocation());
assertNull(script.vendor);
}
@Test
public void createsVendorScript() throws Exception {
final Location location = new Location("filesystem:schema");
final Resource resource = new FileSystemResource("schema/5.0.0-M7/postgresql/v1__init.sql");
final SqlScriptCreator scriptsCreator = new SqlScriptCreator();
final SqlScript script = scriptsCreator.createScript(location, resource);
assertEquals(script.name, "v1__init.sql");
assertEquals(script.location, location);
assertEquals(script.dir, "5.0.0-M7");
assertEquals(script.resource.getLocation(), resource.getLocation());
assertEquals(script.vendor, "postgresql");
}
@Test(expectedExceptions = FlywayException.class)
public void failsToCreateResourceWhenPathIsInvalid() throws Exception {
final Location location = new Location("filesystem:schema");
final Resource resource = new FileSystemResource("schema/v1__init.sql");
new SqlScriptCreator().createScript(location, resource);
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2012-2018 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 org.eclipse.che.core.db.schema.impl.flyway;
import static org.testng.Assert.assertEquals;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.internal.util.Location;
import org.flywaydb.core.internal.util.scanner.filesystem.FileSystemResource;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/**
* Tests {@link VersionResolver}.
*
* @author Yevhenii Voevodin
*/
public class VersionResolverTest {
private final Flyway flyway = new Flyway();
private final VersionResolver resolver = new VersionResolver();
@BeforeMethod
public void setUpDefaults() {
flyway.setSqlMigrationSuffix(".sql");
flyway.setSqlMigrationPrefix("");
flyway.setSqlMigrationSeparator("__");
}
@Test(dataProvider = "validScripts")
public void resolvesVersion(String dir, String name, String expectedVersion) {
final SqlScript script =
new SqlScript(
new FileSystemResource("sql/" + dir + "/" + name),
new Location("filesystem:sql"),
dir,
null,
name);
assertEquals(resolver.resolve(script, flyway), MigrationVersion.fromVersion(expectedVersion));
}
@Test(dataProvider = "invalidScripts", expectedExceptions = FlywayException.class)
public void failsToResolveVersions(String dir, String name) throws Exception {
final SqlScript script =
new SqlScript(
new FileSystemResource("sql/" + dir + "/" + name),
new Location("filesystem:sql"),
dir,
null,
name);
resolver.resolve(script, flyway);
}
@DataProvider
public static Object[][] invalidScripts() {
return new String[][] {
{"1.0", "2016-11-11__init.sql"},
{"1.0", "one__init.sql"},
{"1.0", "__init.sql"},
{"1.0", "version1__script.sql"},
{"1.0", "1..1__script.sql"},
{"5..0.0", "1__init.sql"}
};
}
@DataProvider
public static Object[][] validScripts() {
return new Object[][] {
{"5.0.0-M7", "1__init.sql", "5.0.0.7.1"},
{"5.0.0-M7", "1.1__init_sub_tables.sql", "5.0.0.7.1.1"},
{"6.0", "0.1__specific_update.sql", "6.0.0.1"},
{"1.0", "1__simple.sql", "1.0.1"}
};
}
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-2018 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
-->
<configuration>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n</pattern>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.FileAppender">
<File>target/log/codenvy.log</File>
<encoder>
<pattern>%-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n</pattern>
</encoder>
</appender>
<root level="ERROR">
<appender-ref ref="stdout"/>
<appender-ref ref="file"/>
</root>
</configuration>

View File

@ -0,0 +1,18 @@
--
-- Copyright (c) 2012-2018 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
--
CREATE TABLE test (
id INT,
text TEXT,
PRIMARY KEY (id)
);

View File

@ -0,0 +1,15 @@
--
-- Copyright (c) 2012-2018 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
--
INSERT INTO test VALUES(1, 'test1');
INSERT INTO test VALUES(2, 'test2');
INSERT INTO test VALUES(3, 'test3');

View File

@ -0,0 +1,12 @@
--
-- Copyright (c) 2012-2018 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
--

View File

@ -0,0 +1,12 @@
--
-- Copyright (c) 2012-2018 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
--

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-logback</artifactId>
<packaging>jar</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-metrics-core</artifactId>
<name>Che Core :: Commons :: Metrics :: Core</name>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-tracing-core</artifactId>
<name>Che Core :: Commons :: Tracing :: Core</name>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-tracing-metrics</artifactId>
<name>Che Core :: Commons :: Tracing :: Metrics</name>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-tracing-web</artifactId>
<name>Che Core :: Commons :: Tracing :: Web</name>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-typescript-dto-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-commons-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-commons-annotations</artifactId>
<packaging>jar</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-commons-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-commons-inject</artifactId>
<packaging>jar</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-commons-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-commons-j2ee</artifactId>
<packaging>jar</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-commons-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-commons-json</artifactId>
<packaging>jar</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-commons-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-commons-lang</artifactId>
<packaging>jar</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-commons-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-commons-observability</artifactId>
<name>Che Core :: Commons :: Tracing and Monitoring wrapper</name>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-commons-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-commons-schedule</artifactId>
<packaging>jar</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-commons-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-commons-test</artifactId>
<packaging>jar</packaging>

View File

@ -17,7 +17,7 @@
<parent>
<artifactId>che-core-commons-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.86.0-SNAPSHOT</version>
<version>7.80.0</version>
</parent>
<artifactId>che-core-commons-tracing</artifactId>
<name>Che Core :: Commons :: Tracing</name>

Some files were not shown because too many files have changed in this diff Show More