diff --git a/helm-chart/README.rst b/helm-chart/README.rst index 0af49f6f28..3e7e4cee2a 100644 --- a/helm-chart/README.rst +++ b/helm-chart/README.rst @@ -6,7 +6,7 @@ Provide a basic chart for deploying Renku UI application. Configuration ------------- -- `baseUrl` define the URL on the application will be available +- `client.url` define the URL on the application will be available (default: `http://localhost:3000`) - `gitlabUrl` define the URL of a running GitLab instance (default: `http://gitlab.renku.build`) diff --git a/helm-chart/minikube-values.yaml b/helm-chart/minikube-values.yaml index 5543a3cbd9..fa0869948f 100644 --- a/helm-chart/minikube-values.yaml +++ b/helm-chart/minikube-values.yaml @@ -7,6 +7,7 @@ global: clientSecret: "secret" ui: - baseUrl: "http://localhost:3000" + client: + url: "http://localhost:3000" ingress: enabled: false diff --git a/helm-chart/renku-ui/templates/ui-client-deployment-template.yaml b/helm-chart/renku-ui/templates/ui-client-deployment-template.yaml index 3c43ba094d..78de44260b 100644 --- a/helm-chart/renku-ui/templates/ui-client-deployment-template.yaml +++ b/helm-chart/renku-ui/templates/ui-client-deployment-template.yaml @@ -44,11 +44,11 @@ spec: {{- end }} env: - name: BASE_URL - value: {{ .Values.baseUrl | default (printf "%s://%s" (include "ui.protocol" .) .Values.global.renku.domain) | quote }} + value: {{ .Values.client.url | default (printf "%s://%s" (include "ui.protocol" .) .Values.global.renku.domain) | quote }} - name: GATEWAY_URL - value: {{ .Values.gatewayUrl | default (printf "%s://%s/api" (include "ui.protocol" .) .Values.global.renku.domain) | quote }} + value: {{ .Values.gateway.url | default (printf "%s://%s/api" (include "ui.protocol" .) .Values.global.renku.domain) | quote }} - name: UISERVER_URL - value: {{ .Values.client.uiserverUrl | default (printf "%s://%s/ui-server" (include "ui.protocol" .) .Values.global.renku.domain) | quote }} + value: {{ .Values.server.url | default (printf "%s://%s" (include "ui.protocol" .) .Values.global.renku.domain) | printf "%s/ui-server" | quote }} - name: WELCOME_PAGE value: {{ .Values.client.welcomePage.text | b64enc | quote }} {{- if .Values.client.statuspage }} diff --git a/helm-chart/renku-ui/templates/ui-server-deployment.yaml b/helm-chart/renku-ui/templates/ui-server-deployment.yaml index b286fb5b4d..43c70fa954 100644 --- a/helm-chart/renku-ui/templates/ui-server-deployment.yaml +++ b/helm-chart/renku-ui/templates/ui-server-deployment.yaml @@ -40,21 +40,17 @@ spec: protocol: TCP env: - name: SERVER_URL - value: {{ .Values.server.serverData.url | default (printf "https://%s" .Values.global.renku.domain) | quote }} + value: {{ .Values.server.url | default (printf "%s://%s" (include "ui.protocol" .) .Values.global.renku.domain) | quote }} - name: UI_SERVER_VERSION value: {{ .Chart.Version | quote }} - - name: SERVER_PORT - value: {{ .Values.server.serverData.port | default (printf "8080") | quote }} - - name: SERVER_PREFIX - value: {{ .Values.server.serverData.prefix | default (printf "/ui-server") | quote }} - name: GATEWAY_URL - value: {{ .Values.server.gateway.url | default (printf "https://%s/api" .Values.global.renku.domain) | quote }} + value: {{ .Values.gateway.url | default (printf "%s://%s/api" (include "ui.protocol" .) .Values.global.renku.domain) | quote }} - name: GATEWAY_LOGIN_PATH - value: {{ .Values.server.gateway.loginSuffix | default (printf "/auth/login") | quote }} + value: {{ .Values.gateway.loginSuffix | default (printf "/auth/login") | quote }} - name: GATEWAY_LOGOUT_PATH - value: {{ .Values.server.gateway.logoutSuffix | default (printf "/auth/logout") | quote }} + value: {{ .Values.gateway.logoutSuffix | default (printf "/auth/logout") | quote }} - name: AUTH_SERVER_URL - value: {{ .Values.server.authentication.url | default (printf "https://%s/auth/realms/Renku" .Values.global.renku.domain) | quote }} + value: {{ .Values.server.authentication.url | default (printf "%s://%s/auth/realms/Renku" (include "ui.protocol" .) .Values.global.renku.domain) | quote }} - name: AUTH_CLIENT_ID value: {{ .Values.server.authentication.id | default (printf "renku-ui") | quote }} - name: AUTH_CLIENT_SECRET diff --git a/helm-chart/renku-ui/values.yaml b/helm-chart/renku-ui/values.yaml index e5a25f4772..af86fac9d3 100644 --- a/helm-chart/renku-ui/values.yaml +++ b/helm-chart/renku-ui/values.yaml @@ -39,7 +39,14 @@ global: existingSecret: redis-secret existingSecretPasswordKey: redis-password +gateway: + url: + loginSuffix: /auth/login + logoutSuffix: /auth/logout + client: + ## The URL for the client + url: replicaCount: 1 image: @@ -166,6 +173,8 @@ client: tag: "0.11.16" server: + ## The URL for the server service; the URL used by the client is the server.url + /ui-server endpoint. + url: replicaCount: 1 image: @@ -228,16 +237,6 @@ server: affinity: {} - serverData: - url: - port: 8080 - prefix: /ui-server - - gateway: - url: - loginSuffix: /auth/login - logoutSuffix: /auth/logout - sentry: enabled: false dsn: "" diff --git a/server/src/config.ts b/server/src/config.ts index e1f33a36e0..39690ec148 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -16,25 +16,30 @@ * limitations under the License. */ -import { convertType } from "./utils"; - +import { convertType, urlJoin } from "./utils"; const SERVER = { url: process.env.SERVER_URL, - port: convertType(process.env.SERVER_PORT) || 8080, - prefix: process.env.SERVER_PREFIX || "/ui-server", + port: 8080, + prefix: "/ui-server", logLevel: process.env.SERVER_LOG_LEVEL || "info", serverUiVersion: process.env.UI_SERVER_VERSION || "unknown", proxyTimeout: 600 * 1000, // in milliseconds - wsSuffix: "/ws" + wsSuffix: "/ws", }; +const gatewayUrl = process.env.GATEWAY_URL || urlJoin(SERVER.url ?? "", "/api"); + const DEPLOYMENT = { - gatewayUrl: process.env.GATEWAY_URL || SERVER.url + "/api", - gatewayLoginUrl: - (process.env.GATEWAY_URL || SERVER.url + "/api") + (process.env.GATEWAY_LOGIN_PATH || "/auth/login"), - gatewayLogoutUrl: - (process.env.GATEWAY_URL || SERVER.url + "/api") + (process.env.GATEWAY_LOGOUT_PATH || "/auth/logout"), + gatewayUrl, + gatewayLoginUrl: urlJoin( + gatewayUrl, + process.env.GATEWAY_LOGIN_PATH || "/auth/login" + ), + gatewayLogoutUrl: urlJoin( + gatewayUrl, + process.env.GATEWAY_LOGOUT_PATH || "/auth/logout" + ), }; const SENTRY = { @@ -43,7 +48,7 @@ const SENTRY = { namespace: process.env.SENTRY_NAMESPACE || undefined, telepresence: !!process.env.TELEPRESENCE, sampleRate: parseFloat(process.env.SENTRY_TRACE_RATE) || 0, - debugMode: [true, "true"].includes(process.env.SENTRY_DEBUG) + debugMode: [true, "true"].includes(process.env.SENTRY_DEBUG), }; const AUTHENTICATION = { @@ -97,7 +102,7 @@ const config = { routes: ROUTES, sentry: SENTRY, server: SERVER, - websocket: WEBSOCKET + websocket: WEBSOCKET, }; export default config; diff --git a/server/src/utils/index.ts b/server/src/utils/index.ts index 73ff78ceab..e8ed60efb9 100644 --- a/server/src/utils/index.ts +++ b/server/src/utils/index.ts @@ -16,6 +16,8 @@ * limitations under the License. */ +import urlJoin from "./url-join"; + /** * Convert string to booloan or numbers. Useful to handle values coming from environmental variables. @@ -134,4 +136,4 @@ function simpleHash(str: string, seed = 0): number { return 4294967296 * (2097151 & h2) + (h1 >>> 0); } -export { clamp, convertType, getCookieValueByName, getRelease, simpleHash, sleep }; +export { clamp, convertType, getCookieValueByName, getRelease, simpleHash, sleep, urlJoin }; diff --git a/server/src/utils/url-join.ts b/server/src/utils/url-join.ts new file mode 100644 index 0000000000..71b5e561e2 --- /dev/null +++ b/server/src/utils/url-join.ts @@ -0,0 +1,70 @@ +/* eslint-disable brace-style */ +/* eslint-disable no-useless-escape */ +/* eslint-disable curly */ + +/** url-join library from https://github.com/jfromaniello/url-join/blob/main/lib/url-join.js */ +function normalize(strArray: string[]) { + const resultArray = []; + if (strArray.length === 0) { + return ""; + } + + if (typeof strArray[0] !== "string") { + throw new TypeError("Url must be a string. Received " + strArray[0]); + } + + // If the first part is a plain protocol, we combine it with the next part. + if (strArray[0].match(/^[^/:]+:\/*$/) && strArray.length > 1) { + strArray[0] = strArray.shift() + strArray[0]; + } + + // There must be two or three slashes in the file protocol, two slashes in anything else. + if (strArray[0].match(/^file:\/\/\//)) { + strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, "$1:///"); + } else { + strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, "$1://"); + } + + for (let i = 0; i < strArray.length; i++) { + let component = strArray[i]; + + if (typeof component !== "string") { + throw new TypeError("Url must be a string. Received " + component); + } + + if (component === "") { + continue; + } + + if (i > 0) { + // Removing the starting slashes for each component but the first. + component = component.replace(/^[\/]+/, ""); + } + if (i < strArray.length - 1) { + // Removing the ending slashes for each component but the last. + component = component.replace(/[\/]+$/, ""); + } else { + // For the last component we will combine multiple slashes to a single one. + component = component.replace(/[\/]+$/, "/"); + } + + resultArray.push(component); + } + + let str = resultArray.join("/"); + // Each input component is now separated by a single slash except the possible first plain protocol part. + + // remove trailing slash before parameters or hash + str = str.replace(/\/(\?|&|#[^!])/g, "$1"); + + // replace ? in parameters with & + const parts = str.split("?"); + str = parts.shift() + (parts.length > 0 ? "?" : "") + parts.join("&"); + + return str; +} + +export default function urlJoin(...args: string[]): string { + const parts = Array.from(Array.isArray(args[0]) ? args[0] : args); + return normalize(parts); +}