From a811045d097256c28b611f1984718aa90fe2c657 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Fri, 6 May 2022 10:48:45 +0200 Subject: [PATCH 01/33] MGMT-10246: Suport custom-provided TLS certificates - backend part --- ui/backend/src/common/domains.ts | 2 + ui/backend/src/common/index.ts | 1 + ui/backend/src/common/types.ts | 8 ++ ui/backend/src/endpoints/changeDomain.ts | 82 +++++++++++++------ ui/backend/src/k8s/oauth.ts | 2 +- .../components/PersistPage/persistDomain.ts | 18 ++-- 6 files changed, 83 insertions(+), 30 deletions(-) create mode 100644 ui/backend/src/common/domains.ts diff --git a/ui/backend/src/common/domains.ts b/ui/backend/src/common/domains.ts new file mode 100644 index 000000000..2b7caff45 --- /dev/null +++ b/ui/backend/src/common/domains.ts @@ -0,0 +1,2 @@ +export const getApiDomain = (domain: string) => `api.${domain}`; +export const getIngressDomain = (domain: string) => `apps.${domain}`; diff --git a/ui/backend/src/common/index.ts b/ui/backend/src/common/index.ts index 4661fc58f..16118554f 100644 --- a/ui/backend/src/common/index.ts +++ b/ui/backend/src/common/index.ts @@ -1,3 +1,4 @@ export * from './types'; export * from './validation'; export * from './resources'; +export * from './domains'; diff --git a/ui/backend/src/common/types.ts b/ui/backend/src/common/types.ts index 12685aa22..4fdc5103d 100644 --- a/ui/backend/src/common/types.ts +++ b/ui/backend/src/common/types.ts @@ -2,3 +2,11 @@ export type TlsCertificate = { 'tls.crt': string; 'tls.key': string; }; + +export type ChangeDomainInputType = { + clusterDomain?: string; + customCerts: { + domain: string; + certificate: TlsCertificate; + }[]; +}; diff --git a/ui/backend/src/endpoints/changeDomain.ts b/ui/backend/src/endpoints/changeDomain.ts index 57d35c3c2..b35f93599 100644 --- a/ui/backend/src/endpoints/changeDomain.ts +++ b/ui/backend/src/endpoints/changeDomain.ts @@ -1,6 +1,12 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ import { Request, Response } from 'express'; -import { Route } from '../common'; +import { + Route, + ChangeDomainInputType, + getApiDomain, + getIngressDomain, + TlsCertificate, +} from '../common'; import { ZTPFW_DEPLOYMENT_NAME, ZTPFW_NAMESPACE, @@ -21,13 +27,18 @@ import { validateInput } from './utils'; const logger = console; -const createSelfSignedTlsSecret = async ( +const createTlsSecret = async ( res: Response, token: string, domain: string, namePrefix: string, + customCerts: ChangeDomainInputType['customCerts'] = [], ): Promise => { - const certificate = await generateCertificate(res, domain); + let certificate: TlsCertificate | undefined = customCerts.find( + (c) => c.domain === domain, + )?.certificate; + // not provided, so generate self-signed one + certificate = certificate || (await generateCertificate(res, domain)); if (!certificate) { return undefined; } @@ -43,6 +54,7 @@ const createSelfSignedTlsSecret = async ( const updateIngressComponentRoutes = async ( res: Response, token: string, + customCerts: ChangeDomainInputType['customCerts'], componentRoutes: ComponentRoute[], // Following type will not be hardcoded forever, the ZTPFW hostname might varry over different deployments routeName: 'console' | 'oauth-openshift' | 'edge-cluster-setup', @@ -50,7 +62,7 @@ const updateIngressComponentRoutes = async ( namePrefix: string, routeNamespace: string, ): Promise => { - const secretName = await createSelfSignedTlsSecret(res, token, domain, namePrefix); + const secretName = await createTlsSecret(res, token, domain, namePrefix, customCerts); if (secretName) { const route = componentRoutes.find((r) => r.name === routeName); if (route) { @@ -239,6 +251,26 @@ const updateZtpfwUI = async ( await updateZtpfwDeployment(token, ztpfwDomain, newOauthRedirectUri); }; +const isDomainChanged = ( + res: Response, + apiDomain: string, + newDomain: string, + oldDomain?: string, +) => { + if (oldDomain === newDomain) { + logger.info( + 'Domain stays unchanged (based on the Ingress config), skipping persistence of it.', + ); + res.writeHead(200).end(); // All good + return false; + } + logger.debug( + `About to change domain from "${oldDomain}" to "${newDomain}" (api: "${apiDomain}")`, + ); + + return true; +}; + /** * Will perform cluster domain change. * Intentionally executed on the backend to decrease risks of network issues during the complex flow. @@ -267,36 +299,37 @@ const updateZtpfwUI = async ( * - side-effect: our pod is terminated (consequence of the Deployment resource change) * */ -const changeDomainImpl = async (res: Response, token: string, _domain?: string): Promise => { - const domain = validateInput(DNS_NAME_REGEX, _domain); - logger.debug('ChangeDomain endpoint called, domain:', domain); +const changeDomainImpl = async ( + res: Response, + token: string, + input: ChangeDomainInputType, +): Promise => { + const clusterDomain = validateInput(DNS_NAME_REGEX, input.clusterDomain); + logger.debug('ChangeDomain endpoint called, domain:', clusterDomain); - if (!domain) { + if (!clusterDomain) { res.writeHead(422).end(); return; } - /* TODO: avoid auto-generating of self-signed certificates if the user has provided them */ - const ingress = await getIngressConfig(token); const oldIngressDomain = ingress.spec?.domain; - const apiDomain = `api.${domain}`; - const ingressDomain = `apps.${domain}`; + const apiDomain = getApiDomain(clusterDomain); + const ingressDomain = getIngressDomain(clusterDomain); - if (ingressDomain === ingress?.spec?.domain) { - logger.info( - 'Domain stays unchanged (based on the Ingress config), skipping persistence of it.', - ); - res.writeHead(200).end(); // All good + if (!isDomainChanged(res, apiDomain, ingressDomain, oldIngressDomain)) { return; } - logger.debug( - `About to change domain from "${oldIngressDomain}" to "${ingressDomain}" (api: "${apiDomain}")`, - ); // Prepare patch to change API certificate (apiserver/cluster resource) - will be executed at the end of the flow - const apiCertSecretName = await createSelfSignedTlsSecret(res, token, apiDomain, 'api-secret-'); + const apiCertSecretName = await createTlsSecret( + res, + token, + apiDomain, + 'api-secret-', + input.customCerts, + ); if (!apiCertSecretName) { return; } @@ -322,6 +355,7 @@ const changeDomainImpl = async (res: Response, token: string, _domain?: string): const consoleTlsSecretName = await updateIngressComponentRoutes( res, token, + input.customCerts, componentRoutes, 'console', consoleDomain, @@ -331,6 +365,7 @@ const changeDomainImpl = async (res: Response, token: string, _domain?: string): const oauthTlsSecretName = await updateIngressComponentRoutes( res, token, + input.customCerts, componentRoutes, 'oauth-openshift', oauthDomain, @@ -340,6 +375,7 @@ const changeDomainImpl = async (res: Response, token: string, _domain?: string): const ztpfwUiTlsSecretName = await updateIngressComponentRoutes( res, token, + input.customCerts, componentRoutes, ZTPFW_UI_ROUTE_PREFIX, ztpfwDomain, @@ -428,8 +464,8 @@ export function changeDomain(req: Request, res: Response): void { .on('end', async () => { try { const data: string = Buffer.concat(body).toString(); - const encoded = JSON.parse(data) as { domain?: string }; - await changeDomainImpl(res, token, encoded?.domain); + const encoded = JSON.parse(data) as ChangeDomainInputType; + await changeDomainImpl(res, token, encoded); } catch (e) { logger.error('Failed to parse input for changeDomain'); res.writeHead(422).end(); diff --git a/ui/backend/src/k8s/oauth.ts b/ui/backend/src/k8s/oauth.ts index 45f6a64ca..f3ba1d906 100644 --- a/ui/backend/src/k8s/oauth.ts +++ b/ui/backend/src/k8s/oauth.ts @@ -146,8 +146,8 @@ export async function logout(req: Request, res: Response): Promise { const host = req.headers.host; - deleteCookie(res, { cookie: 'connect.sid' }); deleteCookie(res, { cookie: K8S_ACCESS_TOKEN_COOKIE }); + deleteCookie(res, { cookie: 'connect.sid' }); deleteCookie(res, { cookie: '_oauth_proxy', domain: `.${host || ''}` }); res.writeHead(200).end(); } diff --git a/ui/frontend/src/components/PersistPage/persistDomain.ts b/ui/frontend/src/components/PersistPage/persistDomain.ts index f69a02cea..b33702c5a 100644 --- a/ui/frontend/src/components/PersistPage/persistDomain.ts +++ b/ui/frontend/src/components/PersistPage/persistDomain.ts @@ -2,28 +2,34 @@ import { postRequest } from '../../resources'; import { PersistSteps, UsePersistProgressType } from '../PersistProgress'; import { PERSIST_DOMAIN } from './constants'; import { PersistErrorType } from './types'; +import { ChangeDomainInputType } from '../../backend-shared'; export const persistDomain = async ( setError: (error: PersistErrorType) => void, setProgress: UsePersistProgressType['setProgress'], - domain?: string, + clusterDomain?: string, ): Promise => { - if (!domain) { + if (!clusterDomain) { console.info('Domain change not requested, so skipping that step.'); setProgress(PersistSteps.PersistDomain); return true; // skip } + const input: ChangeDomainInputType = { + clusterDomain, + customCerts: [ + // TODO: Add custom-provided certificates here + ], + }; + try { // Due to complexity, the flow has been moved to backend to decrease risks related to network communication - await postRequest('/changeDomain', { - domain, - }).promise; + await postRequest('/changeDomain', input).promise; } catch (e) { console.error(e); setError({ title: PERSIST_DOMAIN, - message: `Failed to change the cluster domain to "${domain}".`, + message: `Failed to change the cluster domain to "${clusterDomain}".`, }); return false; } From 0859c6d658676504aea2794004a25e89da6b480c Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Thu, 19 May 2022 10:53:27 +0200 Subject: [PATCH 02/33] MGMT-10246: Suport custom-provided TLS certificates - frontend part --- ui/backend/src/common/domains.ts | 8 + ui/backend/src/common/types.ts | 10 +- ui/backend/src/constants.ts | 1 - ui/backend/src/endpoints/changeDomain.ts | 32 +-- .../src/endpoints/generateCertificate.ts | 30 +++ ui/backend/src/k8s/oauth.ts | 3 +- .../DomainCertificatesDecision.tsx | 46 ++++ .../DomainCertificatesDecisionPage.tsx | 37 ++++ .../DomainCertificatesDecisionPage/index.ts | 1 + .../DomainCertificates.css | 26 +++ .../DomainCertificates.tsx | 201 ++++++++++++++++++ .../DomainCertificatesPage.tsx | 34 +++ .../DomainCertificatesPage/index.ts | 1 + .../src/components/DomainPage/DomainPage.tsx | 11 +- .../src/components/K8SStateContext.tsx | 42 +++- .../src/components/PersistPage/constants.ts | 2 - .../src/components/PersistPage/persist.ts | 8 +- .../components/PersistPage/persistDomain.ts | 5 +- .../src/components/PersistPage/types.ts | 10 +- .../src/components/PersistPage/utils.ts | 2 +- .../PersistProgress/PersistProgress.tsx | 2 +- ui/frontend/src/components/Wizard/Wizard.tsx | 4 + .../WizardProgress/WizardProgressContext.tsx | 6 +- ui/frontend/src/components/index.ts | 2 + ui/frontend/src/components/types.ts | 20 +- ui/frontend/src/components/utils.ts | 47 ++++ 26 files changed, 548 insertions(+), 43 deletions(-) create mode 100644 ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx create mode 100644 ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecisionPage.tsx create mode 100644 ui/frontend/src/components/DomainCertificatesDecisionPage/index.ts create mode 100644 ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.css create mode 100644 ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx create mode 100644 ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPage.tsx create mode 100644 ui/frontend/src/components/DomainCertificatesPage/index.ts diff --git a/ui/backend/src/common/domains.ts b/ui/backend/src/common/domains.ts index 2b7caff45..3adaeef8f 100644 --- a/ui/backend/src/common/domains.ts +++ b/ui/backend/src/common/domains.ts @@ -1,2 +1,10 @@ +export const ZTPFW_UI_ROUTE_PREFIX = 'edge-cluster-setup'; + export const getApiDomain = (domain: string) => `api.${domain}`; export const getIngressDomain = (domain: string) => `apps.${domain}`; + +export const getConsoleDomain = (domain: string) => + `console-openshift-console.${getIngressDomain(domain)}`; +export const getOauthDomain = (domain: string) => `oauth-openshift.${getIngressDomain(domain)}`; +export const getZtpfwDomain = (domain: string) => + `${ZTPFW_UI_ROUTE_PREFIX}.${getIngressDomain(domain)}`; diff --git a/ui/backend/src/common/types.ts b/ui/backend/src/common/types.ts index 4fdc5103d..19ebf6a87 100644 --- a/ui/backend/src/common/types.ts +++ b/ui/backend/src/common/types.ts @@ -1,12 +1,14 @@ export type TlsCertificate = { 'tls.crt': string; 'tls.key': string; + + 'tls.crt.filename'?: string; + 'tls.key.filename'?: string; }; export type ChangeDomainInputType = { clusterDomain?: string; - customCerts: { - domain: string; - certificate: TlsCertificate; - }[]; + customCerts?: { + [key: /* ~ domain */ string]: TlsCertificate; + }; }; diff --git a/ui/backend/src/constants.ts b/ui/backend/src/constants.ts index 45e0f2954..56c4c4af0 100644 --- a/ui/backend/src/constants.ts +++ b/ui/backend/src/constants.ts @@ -2,7 +2,6 @@ export const TLS_SECRET_NAMESPACE = 'openshift-config'; // Route-prefix for this application. // TODO: Make it dynamic, this might vary over deployments -export const ZTPFW_UI_ROUTE_PREFIX = 'edge-cluster-setup'; export const OAUTH_ROUTE_PREFIX = 'oauth-openshift'; export const ZTPFW_ROUTE_NAME = 'ztpfw-ui'; export const ZTPFW_DEPLOYMENT_NAME = 'ztpfw-ui'; diff --git a/ui/backend/src/endpoints/changeDomain.ts b/ui/backend/src/endpoints/changeDomain.ts index b35f93599..5d41ae172 100644 --- a/ui/backend/src/endpoints/changeDomain.ts +++ b/ui/backend/src/endpoints/changeDomain.ts @@ -6,13 +6,16 @@ import { getApiDomain, getIngressDomain, TlsCertificate, + getConsoleDomain, + getOauthDomain, + getZtpfwDomain, + ZTPFW_UI_ROUTE_PREFIX, } from '../common'; import { ZTPFW_DEPLOYMENT_NAME, ZTPFW_NAMESPACE, ZTPFW_OAUTHCLIENT_NAME, ZTPFW_ROUTE_NAME, - ZTPFW_UI_ROUTE_PREFIX, } from '../constants'; import { DNS_NAME_REGEX, PatchType, ComponentRoute } from '../frontend-shared'; import { getToken, PostResponse, unauthorized } from '../k8s'; @@ -32,15 +35,19 @@ const createTlsSecret = async ( token: string, domain: string, namePrefix: string, - customCerts: ChangeDomainInputType['customCerts'] = [], + customCerts: ChangeDomainInputType['customCerts'] = {}, ): Promise => { - let certificate: TlsCertificate | undefined = customCerts.find( - (c) => c.domain === domain, - )?.certificate; - // not provided, so generate self-signed one - certificate = certificate || (await generateCertificate(res, domain)); - if (!certificate) { - return undefined; + let certificate: TlsCertificate | undefined = customCerts[domain]; + + // if not provided, so generate self-signed one + if (certificate) { + logger.debug('Custom certificate provided for domain: ', domain); + } else { + certificate = await generateCertificate(res, domain); + + if (!certificate) { + return undefined; + } } const certSecret = await createCertSecret(res, token, namePrefix, certificate); @@ -62,6 +69,7 @@ const updateIngressComponentRoutes = async ( namePrefix: string, routeNamespace: string, ): Promise => { + logger.debug(`updateIngressComponentRoutes called for ${routeName} and domain ${domain}`); const secretName = await createTlsSecret(res, token, domain, namePrefix, customCerts); if (secretName) { const route = componentRoutes.find((r) => r.name === routeName); @@ -347,9 +355,9 @@ const changeDomainImpl = async ( }; // Prepare ingress /cluster resource patch - will be executed at the end of the flow - const consoleDomain = `console-openshift-console.${ingressDomain}`; - const oauthDomain = `oauth-openshift.${ingressDomain}`; - const ztpfwDomain = `${ZTPFW_UI_ROUTE_PREFIX}.${ingressDomain}`; + const consoleDomain = getConsoleDomain(clusterDomain); + const oauthDomain = getOauthDomain(clusterDomain); + const ztpfwDomain = getZtpfwDomain(clusterDomain); const componentRoutes = ingress?.spec?.componentRoutes || []; const consoleTlsSecretName = await updateIngressComponentRoutes( diff --git a/ui/backend/src/endpoints/generateCertificate.ts b/ui/backend/src/endpoints/generateCertificate.ts index d1fa8bf7e..e50947048 100644 --- a/ui/backend/src/endpoints/generateCertificate.ts +++ b/ui/backend/src/endpoints/generateCertificate.ts @@ -66,6 +66,22 @@ export const createCertSecret = async ( const object = cloneDeep(TLS_SECRET); object.metadata.generateName = namePrefix; object.data = certificate; + object.data = { + 'tls.crt': certificate['tls.crt'], + 'tls.key': certificate['tls.key'], + }; + + if (!object.data['tls.crt'] || !object.data['tls.key']) { + res + .writeHead( + 400, + `Can not create ${namePrefix} TLS secret in the ${ + TLS_SECRET.metadata.namespace || '' + } namespace, missing either tls.crt or tls.key.`, + ) + .end(); + return; + } // TODO: what about clean-up? const response = await createSecret(token, object); @@ -74,6 +90,13 @@ export const createCertSecret = async ( return response.body; } + logger.error( + `Can not create ${namePrefix} TLS secret in the ${ + TLS_SECRET.metadata.namespace || '' + } namespace.`, + ' Response: ', + response, + ); res .writeHead( response.statusCode, @@ -83,6 +106,13 @@ export const createCertSecret = async ( ) .end(); } catch (e) { + logger.error( + `Can not create ${namePrefix} TLS secret in the ${ + TLS_SECRET.metadata.namespace || '' + } namespace. Internal error.`, + ' Error: ', + e, + ); res .writeHead( 500, diff --git a/ui/backend/src/k8s/oauth.ts b/ui/backend/src/k8s/oauth.ts index f3ba1d906..7c1a0e499 100644 --- a/ui/backend/src/k8s/oauth.ts +++ b/ui/backend/src/k8s/oauth.ts @@ -8,8 +8,9 @@ import { deleteCookie } from './cookies'; import { jsonRequest } from './json-request'; import { getToken, K8S_ACCESS_TOKEN_COOKIE } from './token'; import { redirect, respondInternalServerError, unauthorized } from './respond'; -import { OAUTH_ROUTE_PREFIX, ZTPFW_UI_ROUTE_PREFIX } from '../constants'; +import { OAUTH_ROUTE_PREFIX } from '../constants'; import { setDead } from '../endpoints'; +import { ZTPFW_UI_ROUTE_PREFIX } from '../common'; const logger = console; diff --git a/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx b/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx new file mode 100644 index 000000000..c285a659b --- /dev/null +++ b/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { Radio, Split, SplitItem, Stack, StackItem, Title } from '@patternfly/react-core'; + +// import './DomainCertificatesDecision.css'; + +export const DomainCertificatesDecision: React.FC<{ + isAutomatic: boolean; + setAutomatic: (isAutomatic: boolean) => void; +}> = ({ isAutomatic, setAutomatic }) => { + return ( + + + How do you want to assign certificates to your domain? + + + Choose whether or not you want to automatically assign self-signed certificates for your + custom domain. + + + + {/* TODO: Improve positioning on the page */} + + setAutomatic(true)} + /> + + + setAutomatic(false)} + /> + + + + + ); +}; diff --git a/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecisionPage.tsx b/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecisionPage.tsx new file mode 100644 index 000000000..f3dedafb7 --- /dev/null +++ b/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecisionPage.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +import { Page } from '../Page'; +import { ContentThreeRows } from '../ContentThreeRows'; +import { WizardProgress, WizardStepType } from '../WizardProgress'; +import { useWizardProgressContext } from '../WizardProgress/WizardProgressContext'; +import { WizardFooter } from '../WizardFooter'; +import { DomainCertificatesDecision } from './DomainCertificatesDecision'; +import { useK8SStateContext } from '../K8SStateContext'; + +export const DomainCertificatesDecisionPage: React.FC = () => { + const { setActiveStep } = useWizardProgressContext(); + const { domainValidation: validation, customCerts } = useK8SStateContext(); + const [isAutomatic, setAutomatic] = React.useState( + () => Object.keys(customCerts || {}).length === 0, + ); + + // No special step for that + React.useEffect(() => setActiveStep('domain'), [setActiveStep]); + + let next: WizardStepType = 'sshkey'; + if (!isAutomatic) { + next = 'domaincertificates'; + } + + return ( + + } + middle={ + + } + bottom={ !validation} />} + /> + + ); +}; diff --git a/ui/frontend/src/components/DomainCertificatesDecisionPage/index.ts b/ui/frontend/src/components/DomainCertificatesDecisionPage/index.ts new file mode 100644 index 000000000..764e60228 --- /dev/null +++ b/ui/frontend/src/components/DomainCertificatesDecisionPage/index.ts @@ -0,0 +1 @@ +export * from './DomainCertificatesDecisionPage'; diff --git a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.css b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.css new file mode 100644 index 000000000..5f355dc31 --- /dev/null +++ b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.css @@ -0,0 +1,26 @@ +.domain-certificates .pf-c-panel__main { + max-height: 16rem !important; +} + +.domain-certificates .pf-c-panel__main-body { + /* TODO: Make this responsive */ + max-width: 60rem; + min-width: 50rem; +} +.domain-certificate__section { + width: 100%; +} + +.domain-certificate-item { + width: 50%; +} + +/* override default wizard style, we have too much content to show */ +.domain-certificate__h1 { + padding-top: 1rem !important; + align-content: center; +} + +.pf-c-form__helper-text.pf-m-error { + color: var(--pf-global--danger-color--100); +} \ No newline at end of file diff --git a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx new file mode 100644 index 000000000..cf09f6cbc --- /dev/null +++ b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx @@ -0,0 +1,201 @@ +import React from 'react'; +import { Buffer } from 'buffer'; +import { + ExpandableSection, + FileUpload, + Split, + SplitItem, + FormGroup, + Panel, + PanelMain, + PanelMainBody, + Stack, + StackItem, + Title, +} from '@patternfly/react-core'; +import { + CheckCircleIcon, + ExclamationTriangleIcon, + ExclamationCircleIcon, +} from '@patternfly/react-icons'; +import { + global_success_color_100 as successColor, + global_warning_color_100 as warningColor, + global_danger_color_100 as dangerColor, +} from '@patternfly/react-tokens'; +import { + getApiDomain, + getConsoleDomain, + getOauthDomain, + getZtpfwDomain, + TlsCertificate, +} from '../../copy-backend-common'; +import { useK8SStateContext } from '../K8SStateContext'; + +import './DomainCertificates.css'; + +type CertificateProps = { + name: string; + domain: string; +}; + +const getTitle = ( + domainCert: TlsCertificate, + name: string, + domain: string, +): React.ReactElement | string => { + let title: React.ReactElement | string; + if (domainCert?.['tls.crt'] && domainCert?.['tls.key']) { + title = ( + <> + {name}: done for {domain} + + ); + } else if (!domainCert?.['tls.crt'] && !domainCert?.['tls.key']) { + title = ( + <> + {name}: generate self-signed for{' '} + {domain} + + ); + } else { + title = ( + <> + {name}: missing for {domain} + + ); + } + + return title; +}; + +const Certificate: React.FC = ({ name, domain }) => { + const [isExpanded, setExpanded] = React.useState(false); + const { customCerts, setCustomCertificate, customCertsValidation } = useK8SStateContext(); + + const { + certValidated, + certLabelHelperText, + certLabelInvalid, + + keyValidated, + keyLabelInvalid, + } = customCertsValidation[domain] || {}; + + const domainCert = customCerts?.[domain] || { 'tls.crt': '', 'tls.key': '' }; + + const idCert = `file-upload-certificate-${name.replaceAll(' ', '')}`; + const idKey = `file-upload-key-${name.replaceAll(' ', '')}`; + + const onChange = async (key: 'tls.crt' | 'tls.key', file: File) => { + const newCert = { ...domainCert }; + newCert[`${key}.filename`] = file.name; + + const fr = new FileReader(); + fr.onload = () => { + newCert[key] = Buffer.from(fr.result as string).toString('base64'); + setCustomCertificate(domain, newCert); + }; + fr.readAsText(file); + }; + + const onClear = (key: 'tls.crt' | 'tls.key') => { + const newCert = { ...domainCert }; + newCert[key] = ''; + newCert[`${key}.filename`] = ''; + setCustomCertificate(domain, newCert); + }; + + return ( + setExpanded(!isExpanded)} + isExpanded={isExpanded} + displaySize="large" + > + + + + onChange('tls.crt', file)} + onClearClick={() => { + onClear('tls.crt'); + }} + /> + + + + + onChange('tls.key', file)} + onClearClick={() => { + onClear('tls.key'); + }} + /> + + + + + ); +}; + +export const DomainCertificates: React.FC = () => { + const { domain } = useK8SStateContext(); + + return ( + <> + + + + Upload your certificates + + + + Secure your domains with SSL/TLS certificates. If a certificate is not provided, a + self-signed one will be automatically generated. + + + + + + + + + + + + + + + + ); +}; diff --git a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPage.tsx b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPage.tsx new file mode 100644 index 000000000..732ffc4de --- /dev/null +++ b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPage.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import { Page } from '../Page'; +import { ContentThreeRows } from '../ContentThreeRows'; +import { WizardProgress } from '../WizardProgress'; +import { useWizardProgressContext } from '../WizardProgress/WizardProgressContext'; +import { WizardFooter } from '../WizardFooter'; +import { DomainCertificates } from './DomainCertificates'; +import { useK8SStateContext } from '../K8SStateContext'; + +export const DomainCertificatesPage: React.FC = () => { + const { setActiveStep } = useWizardProgressContext(); + // No special step for that + React.useEffect(() => setActiveStep('domain'), [setActiveStep]); + const { customCertsValidation } = useK8SStateContext(); + + // Keep enabled for self-signed certs + const isNextEnabled = () => + Object.values(customCertsValidation).every( + (validation) => validation.certValidated !== 'error' && validation.keyValidated !== 'error', + ); + + return ( + + } + middle={} + bottom={ + + } + /> + + ); +}; diff --git a/ui/frontend/src/components/DomainCertificatesPage/index.ts b/ui/frontend/src/components/DomainCertificatesPage/index.ts new file mode 100644 index 000000000..7036a6f50 --- /dev/null +++ b/ui/frontend/src/components/DomainCertificatesPage/index.ts @@ -0,0 +1 @@ +export * from './DomainCertificatesPage'; diff --git a/ui/frontend/src/components/DomainPage/DomainPage.tsx b/ui/frontend/src/components/DomainPage/DomainPage.tsx index 0148b8a8a..be05f052a 100644 --- a/ui/frontend/src/components/DomainPage/DomainPage.tsx +++ b/ui/frontend/src/components/DomainPage/DomainPage.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Page } from '../Page'; import { ContentThreeRows } from '../ContentThreeRows'; -import { WizardProgress } from '../WizardProgress'; +import { WizardProgress, WizardStepType } from '../WizardProgress'; import { useWizardProgressContext } from '../WizardProgress/WizardProgressContext'; import { WizardFooter } from '../WizardFooter'; import { DomainSelector } from './DomainSelector'; @@ -11,14 +11,19 @@ import { useK8SStateContext } from '../K8SStateContext'; export const DomainPage: React.FC = () => { const { setActiveStep } = useWizardProgressContext(); React.useEffect(() => setActiveStep('domain'), [setActiveStep]); - const { domainValidation: validation } = useK8SStateContext(); + const { domainValidation: validation, domain, originalDomain } = useK8SStateContext(); + + let next: WizardStepType = 'sshkey'; + if (domain !== originalDomain) { + next = 'domaincertsdecision'; + } return ( } middle={} - bottom={ !validation} />} + bottom={ !validation} />} /> ); diff --git a/ui/frontend/src/components/K8SStateContext.tsx b/ui/frontend/src/components/K8SStateContext.tsx index ba4165376..88b077221 100644 --- a/ui/frontend/src/components/K8SStateContext.tsx +++ b/ui/frontend/src/components/K8SStateContext.tsx @@ -5,8 +5,11 @@ import { IpTripletSelectorValidationType, K8SStateContextData, K8SStateContextDataFields, + CustomCertsValidationType, } from './types'; +import { ChangeDomainInputType, TlsCertificate } from '../copy-backend-common'; import { + customCertsValidator, domainValidator, ipTripletAddressValidator, ipWithoutDots, @@ -69,12 +72,34 @@ export const K8SStateContextProvider: React.FC<{ ); const [domain, setDomain] = React.useState(''); + const [originalDomain, setOriginalDomain] = React.useState(); const [domainValidation, setDomainValidation] = React.useState(); - const handleSetDomain = React.useCallback((newDomain: string) => { - setDomainValidation(domainValidator(newDomain)); - setDomain(newDomain); - }, []); + const handleSetDomain = React.useCallback( + (newDomain: string) => { + setDomainValidation(domainValidator(newDomain)); + setDomain(newDomain); + if (!originalDomain) { + // Hint: This is expected to be called within initialDataLoad() only + setOriginalDomain(newDomain); + } + }, + [originalDomain], + ); + + const [customCerts, setCustomCerts] = React.useState({}); + const [customCertsValidation, setCustomCertsValidation] = + React.useState({}); + + const setCustomCertificate = React.useCallback( + (domain: string, certificate: TlsCertificate) => { + const newCustomCerts = { ...customCerts }; + newCustomCerts[domain] = certificate; + setCustomCerts(newCustomCerts); + setCustomCertsValidation(customCertsValidator(customCertsValidation, domain, certificate)); + }, + [customCertsValidation, customCerts, setCustomCerts], + ); const fieldValues: K8SStateContextDataFields = React.useMemo( () => ({ @@ -83,8 +108,10 @@ export const K8SStateContextProvider: React.FC<{ apiaddr, ingressIp, domain, + originalDomain, + customCerts, }), - [username, password, apiaddr, ingressIp, domain], + [username, password, apiaddr, ingressIp, domain, originalDomain, customCerts], ); const [snapshot, setSnapshot] = React.useState(); @@ -117,6 +144,9 @@ export const K8SStateContextProvider: React.FC<{ domainValidation, handleSetDomain, + + customCertsValidation, + setCustomCertificate, }), [ fieldValues, @@ -132,6 +162,8 @@ export const K8SStateContextProvider: React.FC<{ handleSetIngressIp, domainValidation, handleSetDomain, + customCertsValidation, + setCustomCertificate, ], ); diff --git a/ui/frontend/src/components/PersistPage/constants.ts b/ui/frontend/src/components/PersistPage/constants.ts index a685a24e3..f988dce68 100644 --- a/ui/frontend/src/components/PersistPage/constants.ts +++ b/ui/frontend/src/components/PersistPage/constants.ts @@ -27,6 +27,4 @@ export const SSH_PRIVATE_KEY_SECRET_INCORRECT = 'Incorrect SSH key secret'; export const ADDRESS_POOL_ANNOTATION_KEY = 'metallb.universe.tf/address-pool'; export const ADDRESS_POOL_NAMESPACE = 'metallb'; -export const ZTPFW_UI_ROUTE_PREFIX = 'edge-cluster-setup'; - export const kubeadminSecret = { name: 'kubeadmin', namespace: 'kube-system' }; diff --git a/ui/frontend/src/components/PersistPage/persist.ts b/ui/frontend/src/components/PersistPage/persist.ts index f5609e2a0..9ab3fa8a1 100644 --- a/ui/frontend/src/components/PersistPage/persist.ts +++ b/ui/frontend/src/components/PersistPage/persist.ts @@ -1,4 +1,4 @@ -import { getCondition } from '../../copy-backend-common'; +import { getCondition, ZTPFW_UI_ROUTE_PREFIX } from '../../copy-backend-common'; import { getClusterOperator } from '../../resources/clusteroperator'; import { PersistSteps, UsePersistProgressType } from '../PersistProgress'; import { K8SStateContextData } from '../types'; @@ -8,7 +8,6 @@ import { MAX_LIVENESS_CHECK_COUNT, UI_POD_NOT_READY, WAIT_ON_OPERATOR_TITLE, - ZTPFW_UI_ROUTE_PREFIX, } from './constants'; import { persistDomain } from './persistDomain'; import { persistIdentityProvider, PersistIdentityProviderResult } from './persistIdentityProvider'; @@ -36,7 +35,7 @@ const waitForClusterOperator = async ( } } catch (e) { console.error('waitForClusterOperator error: ', e); - // keep trying + // do not report, keep trying // setError({ // title: WAIT_ON_OPERATOR_TITLE, @@ -109,10 +108,11 @@ export const persist = async ( state.password, ); if ( + (await persistDomain(setError, setProgress, state.domain, state.customCerts)) && persistIdpResult !== PersistIdentityProviderResult.error && (await saveIngress(setError, setProgress, state.ingressIp)) && (await saveApi(setError, setProgress, state.apiaddr)) && - (await persistDomain(setError, setProgress, state.domain)) + (await persistDomain(setError, setProgress, state.domain, state.customCerts)) ) { // finished with success console.log('Data persisted, blocking progress till reconciled'); diff --git a/ui/frontend/src/components/PersistPage/persistDomain.ts b/ui/frontend/src/components/PersistPage/persistDomain.ts index b33702c5a..8192660e7 100644 --- a/ui/frontend/src/components/PersistPage/persistDomain.ts +++ b/ui/frontend/src/components/PersistPage/persistDomain.ts @@ -8,6 +8,7 @@ export const persistDomain = async ( setError: (error: PersistErrorType) => void, setProgress: UsePersistProgressType['setProgress'], clusterDomain?: string, + customCerts?: ChangeDomainInputType['customCerts'], ): Promise => { if (!clusterDomain) { console.info('Domain change not requested, so skipping that step.'); @@ -17,9 +18,7 @@ export const persistDomain = async ( const input: ChangeDomainInputType = { clusterDomain, - customCerts: [ - // TODO: Add custom-provided certificates here - ], + customCerts, }; try { diff --git a/ui/frontend/src/components/PersistPage/types.ts b/ui/frontend/src/components/PersistPage/types.ts index 2f05434ae..b790b3641 100644 --- a/ui/frontend/src/components/PersistPage/types.ts +++ b/ui/frontend/src/components/PersistPage/types.ts @@ -3,7 +3,9 @@ export type PersistErrorType = { message: string; } | null; -export type TlsCertificate = { - 'tls.crt': string; - 'tls.key': string; -}; +// Remove me +// Take it form common +// export type TlsCertificate = { +// 'tls.crt': string; +// 'tls.key': string; +// }; diff --git a/ui/frontend/src/components/PersistPage/utils.ts b/ui/frontend/src/components/PersistPage/utils.ts index 3e53ba71a..4a88b2514 100644 --- a/ui/frontend/src/components/PersistPage/utils.ts +++ b/ui/frontend/src/components/PersistPage/utils.ts @@ -1,3 +1,4 @@ +import { ZTPFW_UI_ROUTE_PREFIX } from '../../copy-backend-common'; import { getRequest } from '../../resources'; import { getPodsOfNamespace } from '../../resources/pod'; import { delay, getZtpfwUrl } from '../utils'; @@ -5,7 +6,6 @@ import { DELAY_BEFORE_FINAL_REDIRECT, MAX_LIVENESS_CHECK_COUNT, ZTPFW_NAMESPACE, - ZTPFW_UI_ROUTE_PREFIX, } from './constants'; export const waitForLivenessProbe = async ( diff --git a/ui/frontend/src/components/PersistProgress/PersistProgress.tsx b/ui/frontend/src/components/PersistProgress/PersistProgress.tsx index c3d695190..49c475673 100644 --- a/ui/frontend/src/components/PersistProgress/PersistProgress.tsx +++ b/ui/frontend/src/components/PersistProgress/PersistProgress.tsx @@ -29,7 +29,7 @@ PersistStepLabels[PersistSteps.SaveApi] = 'Saving API IP'; PersistStepLabels[PersistSteps.PersistDomain] = 'Saving domain change'; PersistStepLabels[PersistSteps.ReconcileUIPod] = 'Waiting for the configuration pod'; PersistStepLabels[PersistSteps.ReconcileApiOperator] = 'Waiting for the API operator'; -PersistStepLabels[PersistSteps.ReconcileAuthOperator] = 'Waiting for the outhentication operator'; +PersistStepLabels[PersistSteps.ReconcileAuthOperator] = 'Waiting for the authentication operator'; export type UsePersistProgressType = { progress: number; diff --git a/ui/frontend/src/components/Wizard/Wizard.tsx b/ui/frontend/src/components/Wizard/Wizard.tsx index 89f4e7713..3698bae93 100644 --- a/ui/frontend/src/components/Wizard/Wizard.tsx +++ b/ui/frontend/src/components/Wizard/Wizard.tsx @@ -7,6 +7,8 @@ import { ApiAddressPage, IngressIpPage, DomainPage, + DomainCertificatesPage, + DomainCertificatesDecisionPage, PersistPage, FinalPage, } from '../../components'; @@ -25,6 +27,8 @@ export const Wizard: React.FC = () => { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/ui/frontend/src/components/WizardProgress/WizardProgressContext.tsx b/ui/frontend/src/components/WizardProgress/WizardProgressContext.tsx index 40a4d0ce1..c7933eaa8 100644 --- a/ui/frontend/src/components/WizardProgress/WizardProgressContext.tsx +++ b/ui/frontend/src/components/WizardProgress/WizardProgressContext.tsx @@ -11,7 +11,11 @@ export type WizardProgressStepType = | 'ingressip' | 'domain' | 'sshkey'; -export type WizardStepType = WizardProgressStepType | 'persist'; +export type WizardStepType = + | WizardProgressStepType + | 'persist' + | 'domaincertsdecision' + | 'domaincertificates'; export type WizardProgressSteps = { username: WizardProgressStep; diff --git a/ui/frontend/src/components/index.ts b/ui/frontend/src/components/index.ts index d9f04ce42..4ba439307 100644 --- a/ui/frontend/src/components/index.ts +++ b/ui/frontend/src/components/index.ts @@ -7,3 +7,5 @@ export * from './DomainPage'; export * from './PersistPage'; export * from './FinalPage'; export * from './DownloadSshKeyPage'; +export * from './DomainCertificatesDecisionPage'; +export * from './DomainCertificatesPage'; diff --git a/ui/frontend/src/components/types.ts b/ui/frontend/src/components/types.ts index 465be176a..83d7f09a3 100644 --- a/ui/frontend/src/components/types.ts +++ b/ui/frontend/src/components/types.ts @@ -1,4 +1,6 @@ -import { TextInputProps } from '@patternfly/react-core'; +import { FormGroupProps, TextInputProps } from '@patternfly/react-core'; +import { TlsCertificate } from '../copy-backend-common'; +import { ChangeDomainInputType } from '../backend-shared'; export type IpTripletIndex = 0 | 1 | 2 | 3; @@ -19,12 +21,25 @@ export type IpTripletSelectorValidationType = { triplets: IpTripletProps['validated'][]; }; +export type CustomCertsValidationType = { + [key: string]: { + certValidated: FormGroupProps['validated']; + certLabelHelperText?: string; + certLabelInvalid?: string; + + keyValidated: FormGroupProps['validated']; + keyLabelInvalid?: string; + }; +}; + export type K8SStateContextDataFields = { username: string; password: string; apiaddr: string; // 12 characters ingressIp: string; // 12 characters domain: string; + originalDomain?: string; + customCerts: ChangeDomainInputType['customCerts']; }; export type K8SStateContextData = K8SStateContextDataFields & { @@ -45,4 +60,7 @@ export type K8SStateContextData = K8SStateContextDataFields & { handleSetDomain: (newDomain: string) => void; domainValidation?: string; + + setCustomCertificate: (domain: string, certificate: TlsCertificate) => void; + customCertsValidation: CustomCertsValidationType; }; diff --git a/ui/frontend/src/components/utils.ts b/ui/frontend/src/components/utils.ts index 887218201..8c66b2793 100644 --- a/ui/frontend/src/components/utils.ts +++ b/ui/frontend/src/components/utils.ts @@ -1,4 +1,6 @@ +import { FormGroupProps } from '@patternfly/react-core'; import { DNS_NAME_REGEX, USERNAME_REGEX } from '../backend-shared'; +import { TlsCertificate } from '../copy-backend-common'; import { isPasswordPolicyMet } from './PasswordPage/utils'; import { IpTripletSelectorValidationType, K8SStateContextData } from './types'; @@ -77,6 +79,51 @@ export const passwordValidator = (pwd: string): K8SStateContextData['passwordVal return isPasswordPolicyMet(pwd); }; +export const customCertsValidator = ( + oldValidation: K8SStateContextData['customCertsValidation'], + domain: string, + certificate: TlsCertificate, +): K8SStateContextData['customCertsValidation'] => { + const validation: K8SStateContextData['customCertsValidation'] = { ...oldValidation }; + + let certValidated: FormGroupProps['validated'] = 'default'; + let certLabelHelperText = ''; + let certLabelInvalid = ''; + if (!certificate?.['tls.crt'] && certificate?.['tls.key']) { + certValidated = 'error'; + certLabelInvalid = 'Both key and certificate must be provided at once.'; + } else if (!certificate?.['tls.crt']) { + certLabelHelperText = + 'When not uploaded, a self-signed certificate will be generated automatically.'; + } + + let keyValidated: FormGroupProps['validated'] = 'default'; + let keyLabelInvalid = ''; + if (certificate?.['tls.crt'] && !certificate?.['tls.key']) { + keyValidated = 'error'; + keyLabelInvalid = 'Both key and certificate must be provided at once.'; + } + + if (certificate?.['tls.crt'] && certificate?.['tls.key']) { + // TODO: more in-depth content check?? + // -----BEGIN CERTIFICATE----- + // -----BEGIN PRIVATE KEY----- + certValidated = 'success'; + keyValidated = 'success'; + } + + validation[domain] = { + certValidated, + certLabelHelperText, + certLabelInvalid, + + keyValidated, + keyLabelInvalid, + }; + + return validation; +}; + export const ipWithoutDots = (ip?: string): string => { if (ip) { const triplets = ip.split('.'); From 7faaa4ab582826a243fb0e70251cffcd94999598 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Fri, 20 May 2022 11:40:23 +0200 Subject: [PATCH 03/33] Add Buffer to dependencies --- ui/frontend/package.json | 1 + ui/frontend/yarn.lock | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ui/frontend/package.json b/ui/frontend/package.json index a339d3eea..0857c41cc 100644 --- a/ui/frontend/package.json +++ b/ui/frontend/package.json @@ -13,6 +13,7 @@ "@types/node": "^16.7.13", "@types/react": "^17.0.20", "@types/react-dom": "^17.0.9", + "buffer": "^6.0.3", "file-saver": "^2.0.5", "lodash": "^4.17.21", "react": "^17.0.2", diff --git a/ui/frontend/yarn.lock b/ui/frontend/yarn.lock index 3e91e71f6..b0cab22c8 100644 --- a/ui/frontend/yarn.lock +++ b/ui/frontend/yarn.lock @@ -2905,6 +2905,14 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + builtin-modules@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" @@ -5149,7 +5157,7 @@ identity-obj-proxy@^3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== From 6949b97dc158645f1b0c873246cc01acc23000cc Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Thu, 26 May 2022 14:46:40 +0200 Subject: [PATCH 04/33] MGMT-10246: Custom domains on the Settings page --- .../DomainCertificatesDecision.tsx | 63 +- .../DomainCertificates.css | 20 +- .../DomainCertificates.tsx | 209 +- .../DomainCertificatesPanel.tsx | 232 ++ .../PersistPage/PersistPage.test.tsx | 14 +- .../__snapshots__/PersistPage.test.tsx.snap | 6 +- .../src/components/PersistPage/constants.ts | 2 +- .../src/components/PersistPage/types.ts | 7 - .../src/components/Settings/Settings.test.tsx | 2 +- .../SettingsPageDomainCertificates.tsx | 41 + .../components/Settings/SettingsPageRight.css | 18 +- .../components/Settings/SettingsPageRight.tsx | 286 +-- .../__snapshots__/Settings.test.tsx.snap | 1902 +++++++++++------ .../components/WelcomePage/initialDataLoad.ts | 2 + 14 files changed, 1733 insertions(+), 1071 deletions(-) create mode 100644 ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx create mode 100644 ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx diff --git a/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx b/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx index c285a659b..5910acede 100644 --- a/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx +++ b/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx @@ -1,12 +1,43 @@ import React from 'react'; import { Radio, Split, SplitItem, Stack, StackItem, Title } from '@patternfly/react-core'; -// import './DomainCertificatesDecision.css'; - -export const DomainCertificatesDecision: React.FC<{ +type AutomaticManualDecisionProps = { + id?: string; isAutomatic: boolean; setAutomatic: (isAutomatic: boolean) => void; -}> = ({ isAutomatic, setAutomatic }) => { +}; + +export const AutomaticManualDecision: React.FC = ({ + id, + isAutomatic, + setAutomatic, +}) => ( + + {/* TODO: Improve positioning on the Wizard's page */} + + setAutomatic(true)} + /> + + + setAutomatic(false)} + /> + + +); + +export const DomainCertificatesDecision: React.FC = (props) => { return ( @@ -17,29 +48,7 @@ export const DomainCertificatesDecision: React.FC<{ custom domain. - - {/* TODO: Improve positioning on the page */} - - setAutomatic(true)} - /> - - - setAutomatic(false)} - /> - - + ); diff --git a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.css b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.css index 5f355dc31..7d2908fa5 100644 --- a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.css +++ b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.css @@ -1,18 +1,8 @@ .domain-certificates .pf-c-panel__main { max-height: 16rem !important; } - -.domain-certificates .pf-c-panel__main-body { - /* TODO: Make this responsive */ - max-width: 60rem; - min-width: 50rem; -} -.domain-certificate__section { - width: 100%; -} - -.domain-certificate-item { - width: 50%; +.domain-certificate__item { + width: 58rem; } /* override default wizard style, we have too much content to show */ @@ -23,4 +13,8 @@ .pf-c-form__helper-text.pf-m-error { color: var(--pf-global--danger-color--100); -} \ No newline at end of file +} + +.domain-certificates .pf-c-expandable-section__toggle-text { + text-align: left; +} diff --git a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx index cf09f6cbc..96056df96 100644 --- a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx +++ b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx @@ -1,201 +1,24 @@ import React from 'react'; -import { Buffer } from 'buffer'; -import { - ExpandableSection, - FileUpload, - Split, - SplitItem, - FormGroup, - Panel, - PanelMain, - PanelMainBody, - Stack, - StackItem, - Title, -} from '@patternfly/react-core'; -import { - CheckCircleIcon, - ExclamationTriangleIcon, - ExclamationCircleIcon, -} from '@patternfly/react-icons'; -import { - global_success_color_100 as successColor, - global_warning_color_100 as warningColor, - global_danger_color_100 as dangerColor, -} from '@patternfly/react-tokens'; -import { - getApiDomain, - getConsoleDomain, - getOauthDomain, - getZtpfwDomain, - TlsCertificate, -} from '../../copy-backend-common'; -import { useK8SStateContext } from '../K8SStateContext'; +import { Stack, StackItem, Title } from '@patternfly/react-core'; +import { DomainCertificatesPanel } from './DomainCertificatesPanel'; import './DomainCertificates.css'; -type CertificateProps = { - name: string; - domain: string; -}; - -const getTitle = ( - domainCert: TlsCertificate, - name: string, - domain: string, -): React.ReactElement | string => { - let title: React.ReactElement | string; - if (domainCert?.['tls.crt'] && domainCert?.['tls.key']) { - title = ( - <> - {name}: done for {domain} - - ); - } else if (!domainCert?.['tls.crt'] && !domainCert?.['tls.key']) { - title = ( - <> - {name}: generate self-signed for{' '} - {domain} - - ); - } else { - title = ( - <> - {name}: missing for {domain} - - ); - } - - return title; -}; - -const Certificate: React.FC = ({ name, domain }) => { - const [isExpanded, setExpanded] = React.useState(false); - const { customCerts, setCustomCertificate, customCertsValidation } = useK8SStateContext(); - - const { - certValidated, - certLabelHelperText, - certLabelInvalid, - - keyValidated, - keyLabelInvalid, - } = customCertsValidation[domain] || {}; - - const domainCert = customCerts?.[domain] || { 'tls.crt': '', 'tls.key': '' }; - - const idCert = `file-upload-certificate-${name.replaceAll(' ', '')}`; - const idKey = `file-upload-key-${name.replaceAll(' ', '')}`; - - const onChange = async (key: 'tls.crt' | 'tls.key', file: File) => { - const newCert = { ...domainCert }; - newCert[`${key}.filename`] = file.name; - - const fr = new FileReader(); - fr.onload = () => { - newCert[key] = Buffer.from(fr.result as string).toString('base64'); - setCustomCertificate(domain, newCert); - }; - fr.readAsText(file); - }; - - const onClear = (key: 'tls.crt' | 'tls.key') => { - const newCert = { ...domainCert }; - newCert[key] = ''; - newCert[`${key}.filename`] = ''; - setCustomCertificate(domain, newCert); - }; - - return ( - setExpanded(!isExpanded)} - isExpanded={isExpanded} - displaySize="large" - > - - - - onChange('tls.crt', file)} - onClearClick={() => { - onClear('tls.crt'); - }} - /> - - - - - onChange('tls.key', file)} - onClearClick={() => { - onClear('tls.key'); - }} - /> - - - - - ); -}; - export const DomainCertificates: React.FC = () => { - const { domain } = useK8SStateContext(); - return ( - <> - - - - Upload your certificates - - - - Secure your domains with SSL/TLS certificates. If a certificate is not provided, a - self-signed one will be automatically generated. - - - - - - - - - - - - - - - + + + + Upload your certificates + + + + Secure your domains with SSL/TLS certificates. If a certificate is not provided, a + self-signed one will be automatically generated. + + + + + ); }; diff --git a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx new file mode 100644 index 000000000..9b62649e1 --- /dev/null +++ b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx @@ -0,0 +1,232 @@ +import React from 'react'; +import { Buffer } from 'buffer'; +import { + ExpandableSection, + FileUpload, + FormGroup, + Panel, + PanelMain, + PanelMainBody, + FlexItem, + Flex, + FlexProps, + Form, + DescriptionList, + DescriptionListGroup, + DescriptionListTerm, + DescriptionListDescription, + ClipboardCopy, +} from '@patternfly/react-core'; +import { + global_success_color_100 as successColor, + global_warning_color_100 as warningColor, + global_danger_color_100 as dangerColor, +} from '@patternfly/react-tokens'; +import { + CheckCircleIcon, + ExclamationTriangleIcon, + ExclamationCircleIcon, +} from '@patternfly/react-icons'; + +import { + getApiDomain, + getConsoleDomain, + getOauthDomain, + getZtpfwDomain, + TlsCertificate, +} from '../../copy-backend-common'; +import { useK8SStateContext } from '../K8SStateContext'; + +const getTitle = ( + isExpanded: boolean, + domainCert: TlsCertificate, + name: string, + domain: string, +): React.ReactElement | string => { + let title: React.ReactElement | string; + const forDomain = isExpanded ? undefined : <> for {domain}; + + if (domainCert?.['tls.crt'] && domainCert?.['tls.key']) { + title = ( + <> + {name}: done {forDomain} + + ); + } else if (!domainCert?.['tls.crt'] && !domainCert?.['tls.key']) { + title = ( + <> + {name}: generate self-signed{' '} + {forDomain} + + ); + } else { + title = ( + <> + {name}: missing {forDomain} + + ); + } + + return title; +}; + +type CertificateProps = { + name: string; + domain: string; + + isSpaceItemsNone?: boolean; +}; + +const Certificate: React.FC = ({ name, domain, isSpaceItemsNone }) => { + const [isExpanded, setExpanded] = React.useState(false); + const { customCerts, setCustomCertificate, customCertsValidation } = useK8SStateContext(); + + const { + certValidated, + certLabelHelperText, + certLabelInvalid, + + keyValidated, + keyLabelInvalid, + } = customCertsValidation[domain] || {}; + + const domainCert = customCerts?.[domain] || { 'tls.crt': '', 'tls.key': '' }; + + const idCert = `file-upload-certificate-${name.replaceAll(' ', '')}`; + const idKey = `file-upload-key-${name.replaceAll(' ', '')}`; + + const onChange = async (key: 'tls.crt' | 'tls.key', file: File) => { + const newCert = { ...domainCert }; + newCert[`${key}.filename`] = file.name; + + const fr = new FileReader(); + fr.onload = () => { + newCert[key] = Buffer.from(fr.result as string).toString('base64'); + setCustomCertificate(domain, newCert); + }; + fr.readAsText(file); + }; + + const onClear = (key: 'tls.crt' | 'tls.key') => { + const newCert = { ...domainCert }; + newCert[key] = ''; + newCert[`${key}.filename`] = ''; + setCustomCertificate(domain, newCert); + }; + + const spaceItems: FlexProps['spaceItems'] = isSpaceItemsNone + ? { default: 'spaceItemsNone' } + : undefined; + + return ( + setExpanded(!isExpanded)} + isExpanded={isExpanded} + displaySize="large" + > + + + Domain + + + {domain} + + + + + +
+ + + + onChange('tls.crt', file)} + onClearClick={() => { + onClear('tls.crt'); + }} + /> + + + + + + onChange('tls.key', file)} + onClearClick={() => { + onClear('tls.key'); + }} + /> + + + +
+
+ ); +}; + +export const DomainCertificatesPanel: React.FC<{ + isScrollable?: boolean; + isSpaceItemsNone?: boolean; +}> = ({ isScrollable, isSpaceItemsNone }) => { + const { domain } = useK8SStateContext(); + + return ( + + + + + + + + + + + ); +}; diff --git a/ui/frontend/src/components/PersistPage/PersistPage.test.tsx b/ui/frontend/src/components/PersistPage/PersistPage.test.tsx index 24b1494ab..c52b3d305 100644 --- a/ui/frontend/src/components/PersistPage/PersistPage.test.tsx +++ b/ui/frontend/src/components/PersistPage/PersistPage.test.tsx @@ -1,10 +1,12 @@ import React from 'react'; import { render } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; +import { act } from 'react-dom/test-utils'; import { PersistPage } from './PersistPage'; import { K8SStateContextProvider } from '../K8SStateContext'; import { WizardProgressContextProvider } from '../WizardProgress'; +import { delay } from '../../test-utils'; const Component: React.FC = () => ( @@ -17,8 +19,16 @@ const Component: React.FC = () => ( ); describe('PersistPage', () => { - it('can render', () => { - const { container } = render(); + it('can render', async () => { + let container; + + // eslint-disable-next-line testing-library/no-unnecessary-act + await act(async () => { + const { container: c } = render(); + container = c; + await delay(1000); + }); + expect(container).toMatchSnapshot(); // TODO: More complex scenario testing the persist() function on top of mocked data should be implemented }); diff --git a/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap b/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap index f04f1018d..2e699b3c2 100644 --- a/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap +++ b/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap @@ -155,20 +155,20 @@ exports[`PersistPage can render 1`] = ` - Saving Ingress IP + Waiting for the configuration pod
= (props) => ( ); -describe('Seetings', () => { +describe('Settings', () => { it('can render loading state', () => { const { container } = render(); expect(container).toMatchSnapshot(); diff --git a/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx b/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx new file mode 100644 index 000000000..7e8cf0eec --- /dev/null +++ b/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx @@ -0,0 +1,41 @@ +import { FormGroup, StackItem } from '@patternfly/react-core'; +import React from 'react'; +import { AutomaticManualDecision } from '../DomainCertificatesDecisionPage/DomainCertificatesDecision'; +import { DomainCertificatesPanel } from '../DomainCertificatesPage/DomainCertificatesPanel'; + +export const SettingsPageDomainCertificates: React.FC = () => { + const [isAutomatic, setAutomatic] = React.useState(true); + + React.useEffect( + () => { + /* TODO: decide about isutomatic */ + }, + [ + /* Just once */ + ], + ); + + return ( + <> + + + + + + {!isAutomatic && ( + + + + )} + + ); +}; diff --git a/ui/frontend/src/components/Settings/SettingsPageRight.css b/ui/frontend/src/components/Settings/SettingsPageRight.css index 893437cc5..d4428bc3c 100644 --- a/ui/frontend/src/components/Settings/SettingsPageRight.css +++ b/ui/frontend/src/components/Settings/SettingsPageRight.css @@ -1,17 +1,27 @@ -.settings-page-sumamary__form { - height: 100%; -} - .settings-page-sumamary { padding-top: var(--pf-global--spacer--2xl); margin-right: var(--pf-global--spacer--xl); margin-left: var(--pf-global--spacer--xl); } +.settings-page-sumamary__tab { + max-height: 20rem; + overflow: overlay; +} + .settings-page-sumamary__item { padding-bottom: var(--pf-global--spacer--lg); } +.summary-page-sumamary__item .pf-c-panel__main-body { + padding-left: 0; +} + +.settings-page-sumamary #automatic { + /* TODO: make it responsive*/ + padding-left: 25%; +} + .settings-page-sumamary__item__footer { margin-bottom: var(--pf-global--spacer--lg); } diff --git a/ui/frontend/src/components/Settings/SettingsPageRight.tsx b/ui/frontend/src/components/Settings/SettingsPageRight.tsx index a9b5a4ac7..5f17d9b07 100644 --- a/ui/frontend/src/components/Settings/SettingsPageRight.tsx +++ b/ui/frontend/src/components/Settings/SettingsPageRight.tsx @@ -10,6 +10,9 @@ import { StackItem, TextInput, Title, + Tabs, + Tab, + TabTitleText, } from '@patternfly/react-core'; import { navigateToNewDomain, persist, PersistErrorType } from '../PersistPage'; @@ -17,6 +20,7 @@ import { IpTripletsSelector } from '../IpTripletsSelector'; import { useK8SStateContext } from '../K8SStateContext'; import { DeleteKubeadminButton } from './DeleteKubeadminButton'; import { PersistProgress, usePersistProgress } from '../PersistProgress'; +import { SettingsPageDomainCertificates } from './SettingsPageDomainCertificates'; import './SettingsPageRight.css'; @@ -28,6 +32,7 @@ export const SettingsPageRight: React.FC<{ const [isEdit, setEdit] = React.useState(isInitialEdit); const [isSaving, setIsSaving] = React.useState(false); const [_error, setError] = React.useState(); + const [activeTabKey, setActiveTabKey] = React.useState(0); const state = useK8SStateContext(); const progress = usePersistProgress(); @@ -51,6 +56,7 @@ export const SettingsPageRight: React.FC<{ handleSetIngressIp, domain, + originalDomain, domainValidation, handleSetDomain, } = state; @@ -71,6 +77,7 @@ export const SettingsPageRight: React.FC<{ }; const isAfterRedirection = window.location.hash === '#redirected'; + const isDomainChange = originalDomain && originalDomain !== domain; const onCancelEdit = () => { setEdit(false); @@ -80,140 +87,165 @@ export const SettingsPageRight: React.FC<{ const isSaveDisabled = isSaving || !isDirty(); return ( -
- - - Settings - - - + + Settings + + + + setActiveTabKey(tabIndex as number)} + isBox={false} + aria-label="Choose tab to configure" + > + TCP/IP}> + + + + + + + + + +
+ + + +
+
+
+
+ + Domain}> +
+ + + + + + + {isEdit && isDomainChange && } + +
+
+
+
+ + {error && ( + + - -
+ {error.message} +
- - - - + )} + {isSaving && ( + + + {error === null && ( + <> + All changes have been saved, it might take several minutes for cluster to reconcile. + + )} + + )} + {!isSaving && !error && ( + + {/* Just a placeholder */} + )} + {isAfterRedirection && !isSaving && !isEdit && ( - - - + {/* TODO: Do we want to show 100% progressbar here? After redirection, it can be a fake one... */} + All changes have been saved. - {error && ( - - + {!isEdit && ( + <> + {' '} + + )} - {isSaving && ( - - - {error === null && ( - <> - All changes have been saved, it might take several minutes for cluster to reconcile. - - )} - - )} - {!isSaving && !error && ( - - {/* Just a placeholder */} - - )} - {isAfterRedirection && !isSaving && !isEdit && ( - - {/* TODO: Do we want to shouw 100% progressbar here? After redirection, it can be a fake one... */} - All changes have been saved. - + {isEdit && ( + <> + + + )} - - {!isEdit && ( - <> - {' '} - - - )} - {isEdit && ( - <> - - - - )} - -
- + + ); }; diff --git a/ui/frontend/src/components/Settings/__snapshots__/Settings.test.tsx.snap b/ui/frontend/src/components/Settings/__snapshots__/Settings.test.tsx.snap index 9e75d4191..9e241e728 100644 --- a/ui/frontend/src/components/Settings/__snapshots__/Settings.test.tsx.snap +++ b/ui/frontend/src/components/Settings/__snapshots__/Settings.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Seetings can render data 1`] = ` +exports[`Settings can render data 1`] = `
-
-
-

- Settings -

-
+ Settings + +
+
- + +
-
- - +
+ + +
+
+ + +
+
+
- - +
+
+
+ + +
+
+ + +
+
+
-
-
+
-
-
-
- +
+ + Delete kubeadmin + + +
- +
@@ -299,7 +428,7 @@ exports[`Seetings can render data 1`] = ` `; -exports[`Seetings can render data 2`] = ` +exports[`Settings can render data 2`] = `
-
-
-

- Settings -

-
+ Settings + +
+
- + +
-
- - +
+ + +
+
+ + + . + + . + + . + + +
+
+
- - - . - - . - - . - - +
+
+
+ + +
+
+ + + . + + . + + . + + +
+
+
-
-
+
-
-
-
- +
+ + Save + +
- +
@@ -668,7 +926,7 @@ exports[`Seetings can render data 2`] = ` `; -exports[`Seetings can render data 3`] = ` +exports[`Settings can render data 3`] = `
-
-
-

- Settings -

-
+ Settings + +
+
- + +
-
- - +
+ + +
+
+ + +
+
+
- - +
+
+
+ + +
+
+ + +
+
+
-
-
+
-
-
-
- +
+ + Delete kubeadmin + + +
- +
@@ -967,7 +1354,7 @@ exports[`Seetings can render data 3`] = ` `; -exports[`Seetings can render error 1`] = ` +exports[`Settings can render error 1`] = `
-
-
-

- Settings -

-
+ Settings + +
+
- + +
-
- - +
+ + +
+
+ + +
+
+
- - +
+
+
+ + +
+
+ + +
+
+
-
-
+
-
- - -
-
-
+ + + +
-
- -
-

- - Danger alert: - - Connection failed -

- + +
-
-
- + + @@ -1308,7 +1824,7 @@ exports[`Seetings can render error 1`] = ` `; -exports[`Seetings can render loading state 1`] = ` +exports[`Settings can render loading state 1`] = `
{ + console.log('Initial data load'); + let ingressService, apiService, oauth, ingressConfig; try { oauth = await getOAuth().promise; From 7e305672b6f8a8d62579a655cdcb7364d710e465 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 1 Jun 2022 12:12:51 +0200 Subject: [PATCH 05/33] Add UI extensive Request-Response logging --- deploy-ui/manifests/deployment.yaml | 5 +- .../src/endpoints/generateCertificate.ts | 6 +- ui/backend/src/endpoints/proxy.ts | 3 + ui/backend/src/k8s/fetch-retry.ts | 4 + ui/backend/src/k8s/json-request.ts | 39 ++++++---- ui/backend/src/logging.ts | 76 +++++++++++++++++++ .../src/components/PersistPage/persist.ts | 27 +++++-- .../components/Settings/SettingsPageRight.css | 2 +- ui/frontend/src/resources/frontendLogging.ts | 27 +++++++ ui/frontend/src/resources/resource-request.ts | 11 ++- 10 files changed, 170 insertions(+), 30 deletions(-) create mode 100644 ui/backend/src/logging.ts create mode 100644 ui/frontend/src/resources/frontendLogging.ts diff --git a/deploy-ui/manifests/deployment.yaml b/deploy-ui/manifests/deployment.yaml index 4cd07ba0e..bea24c3ef 100644 --- a/deploy-ui/manifests/deployment.yaml +++ b/deploy-ui/manifests/deployment.yaml @@ -35,15 +35,14 @@ spec: value: "3000" - name: FRONTEND_URL value: "$UI_APP_URL" - # Let's compose it via env variables - #- name: CLUSTER_API_URL - # value: https://kubernetes.default.svc:443 - name: OAUTH2_CLIENT_ID value: "ztpfwoauth" - name: OAUTH2_REDIRECT_URL value: "$UI_APP_URL/login/callback" - name: OAUTH2_CLIENT_SECRET value: "ztpfwoauthsecret" + - name: API_LOGGING_ENABLED + value: "false" # or "true" livenessProbe: failureThreshold: 1 httpGet: diff --git a/ui/backend/src/endpoints/generateCertificate.ts b/ui/backend/src/endpoints/generateCertificate.ts index e50947048..d4bab34e3 100644 --- a/ui/backend/src/endpoints/generateCertificate.ts +++ b/ui/backend/src/endpoints/generateCertificate.ts @@ -24,9 +24,9 @@ export const generateCertificate = async ( const delimiter = '-----generateCertificateDelimiter-----'; try { - const { stdout: _stdout } = await exec( - `/usr/bin/openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout ${keyFile} -out ${certFile} -subj "/CN=${domain}" -addext "subjectAltName = DNS:${domain}" && cat ${keyFile} && echo ${delimiter} && cat ${certFile}`, - ); + const command = `/usr/bin/openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout ${keyFile} -out ${certFile} -subj "/CN=${domain}" -addext "subjectAltName = DNS:${domain}" && cat ${keyFile} && echo ${delimiter} && cat ${certFile}`; + logger.debug('Executing: ', command); + const { stdout: _stdout } = await exec(command); rmdirSync(tmpdir, { recursive: true, maxRetries: 5 }); const stdout = _stdout?.toString(); diff --git a/ui/backend/src/endpoints/proxy.ts b/ui/backend/src/endpoints/proxy.ts index ab90a4d7a..d150e6779 100644 --- a/ui/backend/src/endpoints/proxy.ts +++ b/ui/backend/src/endpoints/proxy.ts @@ -6,6 +6,7 @@ import { URL } from 'url'; import { notFound, unauthorized, getToken, respondInternalServerError } from '../k8s'; import { getClusterApiUrl } from '../k8s/utils'; +import { logRequestProxy, logResponseProxy } from '../logging'; const logger = console; @@ -49,9 +50,11 @@ export function proxy(req: Request, res: Response): void { headers, rejectUnauthorized: false, }; + logRequestProxy(options); pipeline( req, request(options, (response) => { + logResponseProxy(response); if (!response) return notFound(req, res); const responseHeaders: OutgoingHttpHeaders = {}; for (const header of proxyResponseHeaders) { diff --git a/ui/backend/src/k8s/fetch-retry.ts b/ui/backend/src/k8s/fetch-retry.ts index 67b8ccfcf..2fad16e41 100644 --- a/ui/backend/src/k8s/fetch-retry.ts +++ b/ui/backend/src/k8s/fetch-retry.ts @@ -1,4 +1,5 @@ import fetch, { RequestInfo, RequestInit, Response } from 'node-fetch'; +import { logRequestError /*, logResponse, logRequest */ } from '../logging'; export function fetchRetry( url: RequestInfo, @@ -20,7 +21,9 @@ export function fetchRetry( return new Promise(function (resolve, reject) { async function fetchAttempt() { try { + // logRequest(url, init); const response = await fetch(url, init); + // logResponse(response, url); switch (response.status) { case 429: // Too Many Requests { @@ -53,6 +56,7 @@ export function fetchRetry( resolve(response); } } catch (err) { + logRequestError(err); if (err instanceof Error) { // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access switch ((err as any).code) { diff --git a/ui/backend/src/k8s/json-request.ts b/ui/backend/src/k8s/json-request.ts index 115175c92..7b24315e5 100644 --- a/ui/backend/src/k8s/json-request.ts +++ b/ui/backend/src/k8s/json-request.ts @@ -1,6 +1,7 @@ import { constants } from 'http2'; import { Agent } from 'https'; import { HeadersInit } from 'node-fetch'; +import { logRequestResponse } from '../logging'; import { fetchRetry } from './fetch-retry'; const { HTTP2_HEADER_CONTENT_TYPE, HTTP2_HEADER_AUTHORIZATION, HTTP2_HEADER_ACCEPT } = constants; @@ -10,9 +11,12 @@ const agent = new Agent({ rejectUnauthorized: false }); export function jsonRequest(url: string, token?: string): Promise { const headers: HeadersInit = { [HTTP2_HEADER_ACCEPT]: 'application/json' }; if (token) headers[HTTP2_HEADER_AUTHORIZATION] = `Bearer ${token}`; - return fetchRetry(url, { headers, agent, compress: true }).then( - (response) => response.json() as unknown as Promise, - ); + const request = { headers, agent, compress: true }; + return fetchRetry(url, request).then(async (response) => { + const result = (await response.json()) as unknown as Promise; + logRequestResponse('GET', url, request, result); + return result; + }); } export interface PostResponse { @@ -30,17 +34,21 @@ export function jsonPost( [HTTP2_HEADER_CONTENT_TYPE]: 'application/json', }; if (token) headers[HTTP2_HEADER_AUTHORIZATION] = `Bearer ${token}`; - return fetchRetry(url, { + + const request = { method: 'POST', headers, agent, body: JSON.stringify(body), compress: true, - }).then(async (response) => { + }; + + return fetchRetry(url, request).then(async (response) => { const result = { statusCode: response.status, body: (await response.json()) as unknown as T, }; + logRequestResponse('POST', url, request, result); return result; }); } @@ -59,38 +67,41 @@ export function jsonPatch( headers['Content-Type'] = 'application/merge-patch+json'; } - return fetchRetry(url, { + const request = { method: 'PATCH', headers, agent, body: JSON.stringify(patches), compress: true, - }).then(async (response) => { + }; + + return fetchRetry(url, request).then(async (response) => { const result = { statusCode: response.status, body: (await response.json()) as unknown as T, }; + + logRequestResponse('PATCH', url, request, result); return result; }); } -export function jsonDelete( - url: string, - token: string, -): Promise> { +export function jsonDelete(url: string, token: string): Promise> { const headers: HeadersInit = {}; headers[HTTP2_HEADER_AUTHORIZATION] = `Bearer ${token}`; - return fetchRetry(url, { + const request = { method: 'DELETE', headers, agent, compress: true, - }).then(async (response) => { + }; + return fetchRetry(url, request).then(async (response) => { const result = { statusCode: response.status, body: (await response.json()) as unknown as T, }; + logRequestResponse('DELETE', url, request, result); return result; }); -} \ No newline at end of file +} diff --git a/ui/backend/src/logging.ts b/ui/backend/src/logging.ts new file mode 100644 index 000000000..bf224ed73 --- /dev/null +++ b/ui/backend/src/logging.ts @@ -0,0 +1,76 @@ +import { IncomingMessage } from 'http'; +import { RequestOptions } from 'https'; +import { RequestInfo, RequestInit, Response } from 'node-fetch'; + +const isAPILoggingEnabled = (): boolean => process.env.API_LOGGING_ENABLED === 'true'; + +export const logRequest = (url: RequestInfo, init?: RequestInit) => { + if (isAPILoggingEnabled()) { + if (!url?.toString()?.endsWith('/apis')) { + console.log('API Request:\n', url, '\ninit:\n', init); + } + } +}; + +export const logResponse = (response: Response, url?: RequestInfo) => { + if (isAPILoggingEnabled()) { + if (!url?.toString()?.endsWith('/apis')) { + // skip logging livenessProbe + console.log('API Response:\n', response); + } + } +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const logRequestError = (err: any) => { + if (isAPILoggingEnabled()) { + console.log('Request error: \n', err); + } +}; + +export const logRequestProxy = (options: RequestOptions) => { + if (isAPILoggingEnabled()) { + console.log('Proxied API Request:\n', options); + } +}; + +export const logResponseProxy = (response: IncomingMessage) => { + if (isAPILoggingEnabled()) { + console.log( + 'Proxied API Response (skipping data):\nstatusCode: ', + response['statusCode'], + '\nstatusMessage: ', + response['statusMessage'], + '\noriginal request:\n', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + response['_httpMessage'], + '\nrawHeaders: ', + response['rawHeaders'], + '\n', + ); + } +}; + +export const logRequestResponse = ( + method: string, + url: RequestInfo, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + request: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + response?: any, +) => { + if (isAPILoggingEnabled()) { + try { + console.log( + `----- ${method} API request to: ${url.toString() || ''}: \n`, + request, + '\nresponse:\n', + response, + '\n----------', + ); + } catch (err) { + console.error(`Error during logging ${method || ''} API request to: ${url.toString() || ''}`); + } + } +}; diff --git a/ui/frontend/src/components/PersistPage/persist.ts b/ui/frontend/src/components/PersistPage/persist.ts index 9ab3fa8a1..1df6c1ec1 100644 --- a/ui/frontend/src/components/PersistPage/persist.ts +++ b/ui/frontend/src/components/PersistPage/persist.ts @@ -54,7 +54,7 @@ const waitForClusterOperator = async ( return false; }; -const waitOnreconciliation = async ( +const waitOnReconciliation = async ( setError: (error: PersistErrorType) => void, setProgress: UsePersistProgressType['setProgress'], state: K8SStateContextData, @@ -88,10 +88,14 @@ const waitOnreconciliation = async ( // TODO: openshift console?? } + if (!(await waitForClusterOperator(setError, 'kube-apiserver'))) { + return false; + } + // Important: keep following aligned with the last reconcile-step setProgress(PersistSteps.ReconcileAuthOperator); - console.info('waitOnreconciliation finished successfully'); + console.info('waitOnReconciliation finished successfully'); return true; }; @@ -107,12 +111,23 @@ export const persist = async ( state.username, state.password, ); + + if (persistIdpResult === PersistIdentityProviderResult.error) { + console.error('Failed to persist IDP, giving up.'); + return; + } + + if (persistIdpResult === PersistIdentityProviderResult.userCreated) { + if (!(await waitForClusterOperator(setError, 'authentication'))) { + return false; + } + } + + console.log('Saving of IDP is over, about to continue with Domain.'); if ( (await persistDomain(setError, setProgress, state.domain, state.customCerts)) && - persistIdpResult !== PersistIdentityProviderResult.error && (await saveIngress(setError, setProgress, state.ingressIp)) && - (await saveApi(setError, setProgress, state.apiaddr)) && - (await persistDomain(setError, setProgress, state.domain, state.customCerts)) + (await saveApi(setError, setProgress, state.apiaddr)) ) { // finished with success console.log('Data persisted, blocking progress till reconciled'); @@ -120,7 +135,7 @@ export const persist = async ( setError(null); // show the green circle of success // TODO: show progress bar while waiting - if (!(await waitOnreconciliation(setError, setProgress, state, persistIdpResult))) { + if (!(await waitOnReconciliation(setError, setProgress, state, persistIdpResult))) { return; } diff --git a/ui/frontend/src/components/Settings/SettingsPageRight.css b/ui/frontend/src/components/Settings/SettingsPageRight.css index d4428bc3c..f50b0f6bb 100644 --- a/ui/frontend/src/components/Settings/SettingsPageRight.css +++ b/ui/frontend/src/components/Settings/SettingsPageRight.css @@ -27,5 +27,5 @@ } .settings-page-sumamary__persist-progress { - width: 28rem; + width: 26rem; } \ No newline at end of file diff --git a/ui/frontend/src/resources/frontendLogging.ts b/ui/frontend/src/resources/frontendLogging.ts new file mode 100644 index 000000000..2ea137e50 --- /dev/null +++ b/ui/frontend/src/resources/frontendLogging.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +window.API_LOGGING = true; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +const isFrontendLoggingEnabled = () => !!window.API_LOGGING; + +export const logFrontendRequest = (url?: string, reqInit?: RequestInit) => { + if (isFrontendLoggingEnabled()) { + console.log(`=== Request for ${url || ''}:\n`, reqInit && JSON.stringify(reqInit)); + } +}; + +export const logFrontendResponse = (url?: string, result?: any) => { + if (isFrontendLoggingEnabled()) { + console.log(`=== Response for ${url || ''}:\n`, JSON.stringify(result)); + } +}; + +if (isFrontendLoggingEnabled()) { + console.warn( + 'DEBUG BUILD ONLY. For production, turn off extensive logging in the frontendLogging.ts', + ); +} diff --git a/ui/frontend/src/resources/resource-request.ts b/ui/frontend/src/resources/resource-request.ts index 5d2ba9e6f..b121bbd01 100644 --- a/ui/frontend/src/resources/resource-request.ts +++ b/ui/frontend/src/resources/resource-request.ts @@ -8,6 +8,7 @@ import { StatusKind, } from '../backend-shared'; import { getZtpfwUrl } from '../components/utils'; +import { logFrontendRequest, logFrontendResponse } from './frontendLogging'; export interface IRequestResult { promise: Promise; @@ -349,7 +350,7 @@ export async function fetchRetry(options: { while (true) { let response: Response | undefined; try { - response = await fetch(options.url, { + const reqInit: RequestInit = { method: options.method ?? 'GET', credentials: 'include', headers, @@ -357,7 +358,9 @@ export async function fetchRetry(options: { signal: options.signal, redirect: 'manual', // mode: "cors", - }); + }; + logFrontendRequest(options.url, reqInit); + response = await fetch(options.url, reqInit); } catch (err) { if (options.signal.aborted) { throw new ResourceError(`Request aborted`, ResourceErrorCode.RequestAborted); @@ -431,11 +434,13 @@ export async function fetchRetry(options: { } if (response.status < 300) { - return { + const result = { headers: response.headers, status: response.status, data: responseData as T, }; + logFrontendResponse(options.url, result); + return result; } switch (response.status) { From 7b377f67691eea33e0d5f9d9dde6bdafe8b009fd Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 13 Jun 2022 15:49:05 +0200 Subject: [PATCH 06/33] Do not change domain but add a new one --- ui/backend/src/endpoints/changeDomain.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/backend/src/endpoints/changeDomain.ts b/ui/backend/src/endpoints/changeDomain.ts index 5d41ae172..9b0865d21 100644 --- a/ui/backend/src/endpoints/changeDomain.ts +++ b/ui/backend/src/endpoints/changeDomain.ts @@ -397,11 +397,11 @@ const changeDomainImpl = async ( } const ingressPatches: PatchType[] = [ - { - op: ingress?.spec?.domain ? 'replace' : 'add', - path: '/spec/domain', - value: ingressDomain, - }, + // { Do not change domain but add a new one + // op: ingress?.spec?.domain ? 'replace' : 'add', + // path: '/spec/domain', + // value: ingressDomain, + // }, { op: ingress?.spec?.componentRoutes ? 'replace' : 'add', path: '/spec/componentRoutes', From 0f498794cbd504766d312e8f9e24384b1e439066 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 15 Jun 2022 09:34:29 +0200 Subject: [PATCH 07/33] Debugging waiting on reconciliation --- ui/frontend/src/components/PersistPage/constants.ts | 2 +- ui/frontend/src/components/PersistPage/persist.ts | 5 ++++- ui/frontend/src/resources/frontendLogging.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ui/frontend/src/components/PersistPage/constants.ts b/ui/frontend/src/components/PersistPage/constants.ts index 6c53f252f..610064671 100644 --- a/ui/frontend/src/components/PersistPage/constants.ts +++ b/ui/frontend/src/components/PersistPage/constants.ts @@ -14,7 +14,7 @@ export const IDENTITY_PROVIDER_NAME = 'ztpfw-htpasswd-idp'; export const ZTPFW_NAMESPACE = 'ztpfw-ui'; export const DELAY_BEFORE_FINAL_REDIRECT = 10 * 1000; -export const DELAY_BEFORE_QUERY_RETRY = 5000; /* ms */ +export const DELAY_BEFORE_QUERY_RETRY = 5 * 1000; /* ms */ export const MAX_LIVENESS_CHECK_COUNT = 20 * ((60 * 1000) / DELAY_BEFORE_QUERY_RETRY); // max 20 minutes export const SSH_PRIVATE_KEY_SECRET = { diff --git a/ui/frontend/src/components/PersistPage/persist.ts b/ui/frontend/src/components/PersistPage/persist.ts index 1df6c1ec1..a676cf6d9 100644 --- a/ui/frontend/src/components/PersistPage/persist.ts +++ b/ui/frontend/src/components/PersistPage/persist.ts @@ -4,6 +4,7 @@ import { PersistSteps, UsePersistProgressType } from '../PersistProgress'; import { K8SStateContextData } from '../types'; import { delay } from '../utils'; import { + DELAY_BEFORE_FINAL_REDIRECT, DELAY_BEFORE_QUERY_RETRY, MAX_LIVENESS_CHECK_COUNT, UI_POD_NOT_READY, @@ -118,6 +119,9 @@ export const persist = async ( } if (persistIdpResult === PersistIdentityProviderResult.userCreated) { + // Let the operator reconciliation start + await delay(DELAY_BEFORE_FINAL_REDIRECT); + if (!(await waitForClusterOperator(setError, 'authentication'))) { return false; } @@ -134,7 +138,6 @@ export const persist = async ( setError(null); // show the green circle of success - // TODO: show progress bar while waiting if (!(await waitOnReconciliation(setError, setProgress, state, persistIdpResult))) { return; } diff --git a/ui/frontend/src/resources/frontendLogging.ts b/ui/frontend/src/resources/frontendLogging.ts index 2ea137e50..aa45593ad 100644 --- a/ui/frontend/src/resources/frontendLogging.ts +++ b/ui/frontend/src/resources/frontendLogging.ts @@ -2,7 +2,7 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -window.API_LOGGING = true; +window.API_LOGGING = false; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore From 5627b090e1573a7df26fc89f0a549640752b26e8 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 15 Jun 2022 13:32:46 +0200 Subject: [PATCH 08/33] Read domain from currentHostnames in status of Ingress --- ui/backend/src/common/domains.ts | 2 ++ ui/backend/src/common/resources/ingress.ts | 8 +++++ ui/backend/src/constants.ts | 1 - ui/backend/src/endpoints/changeDomain.ts | 3 +- ui/backend/src/k8s/oauth.ts | 3 +- .../PersistPage/PersistPageBottom.tsx | 3 ++ .../components/WelcomePage/initialDataLoad.ts | 29 ++++++++++++++----- 7 files changed, 38 insertions(+), 11 deletions(-) diff --git a/ui/backend/src/common/domains.ts b/ui/backend/src/common/domains.ts index 3adaeef8f..6b06627d3 100644 --- a/ui/backend/src/common/domains.ts +++ b/ui/backend/src/common/domains.ts @@ -1,4 +1,6 @@ export const ZTPFW_UI_ROUTE_PREFIX = 'edge-cluster-setup'; +export const OAUTH_ROUTE_PREFIX = 'oauth-openshift'; +export const OAUTH_NAMESPACE = 'openshift-authentication'; export const getApiDomain = (domain: string) => `api.${domain}`; export const getIngressDomain = (domain: string) => `apps.${domain}`; diff --git a/ui/backend/src/common/resources/ingress.ts b/ui/backend/src/common/resources/ingress.ts index 1d3c8c7f3..27a684178 100644 --- a/ui/backend/src/common/resources/ingress.ts +++ b/ui/backend/src/common/resources/ingress.ts @@ -26,4 +26,12 @@ export interface Ingress extends IResource { domain?: string; componentRoutes?: ComponentRoute[]; }; + status?: { + componentRoutes?: { + name: string; + namespace: string; + defaultHostname?: string; + currentHostnames?: string[]; + }[]; + }; } diff --git a/ui/backend/src/constants.ts b/ui/backend/src/constants.ts index 56c4c4af0..6b70a834e 100644 --- a/ui/backend/src/constants.ts +++ b/ui/backend/src/constants.ts @@ -2,7 +2,6 @@ export const TLS_SECRET_NAMESPACE = 'openshift-config'; // Route-prefix for this application. // TODO: Make it dynamic, this might vary over deployments -export const OAUTH_ROUTE_PREFIX = 'oauth-openshift'; export const ZTPFW_ROUTE_NAME = 'ztpfw-ui'; export const ZTPFW_DEPLOYMENT_NAME = 'ztpfw-ui'; diff --git a/ui/backend/src/endpoints/changeDomain.ts b/ui/backend/src/endpoints/changeDomain.ts index 9b0865d21..74de3be6c 100644 --- a/ui/backend/src/endpoints/changeDomain.ts +++ b/ui/backend/src/endpoints/changeDomain.ts @@ -10,6 +10,7 @@ import { getOauthDomain, getZtpfwDomain, ZTPFW_UI_ROUTE_PREFIX, + OAUTH_NAMESPACE, } from '../common'; import { ZTPFW_DEPLOYMENT_NAME, @@ -378,7 +379,7 @@ const changeDomainImpl = async ( 'oauth-openshift', oauthDomain, 'oauth-secret-', - 'openshift-authentication', + OAUTH_NAMESPACE, ); const ztpfwUiTlsSecretName = await updateIngressComponentRoutes( res, diff --git a/ui/backend/src/k8s/oauth.ts b/ui/backend/src/k8s/oauth.ts index 7c1a0e499..7e98b8735 100644 --- a/ui/backend/src/k8s/oauth.ts +++ b/ui/backend/src/k8s/oauth.ts @@ -8,9 +8,8 @@ import { deleteCookie } from './cookies'; import { jsonRequest } from './json-request'; import { getToken, K8S_ACCESS_TOKEN_COOKIE } from './token'; import { redirect, respondInternalServerError, unauthorized } from './respond'; -import { OAUTH_ROUTE_PREFIX } from '../constants'; import { setDead } from '../endpoints'; -import { ZTPFW_UI_ROUTE_PREFIX } from '../common'; +import { OAUTH_ROUTE_PREFIX, ZTPFW_UI_ROUTE_PREFIX } from '../common'; const logger = console; diff --git a/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx b/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx index 8a454faaf..9e7c1b281 100644 --- a/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx +++ b/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx @@ -96,6 +96,9 @@ export const PersistPageBottom: React.FC = () => { Settings succesfully saved, it might take several minutes for cluster to reconcile. + + Please keep this window open until the process is finished. + { + let result = domain?.trim(); + if (result?.startsWith(prefix)) { + result = result.substring(prefix.length); + } + return result; +}; export const initialDataLoad = async ({ setNextPage, @@ -26,7 +35,9 @@ export const initialDataLoad = async ({ }) => { console.log('Initial data load'); - let ingressService, apiService, oauth, ingressConfig; + let ingressService, apiService, oauth; + let ingressConfig: Ingress | undefined; + try { oauth = await getOAuth().promise; ingressService = await getService({ @@ -65,12 +76,16 @@ export const initialDataLoad = async ({ ), ); - let domain = ingressConfig?.spec?.domain?.trim(); - if (domain?.startsWith('apps.')) { - domain = domain.substring('apps.'.length); - } - if (domain) { - handleSetDomain(domain); + const domain = getDomainFromPrefix('apps.', ingressConfig?.spec?.domain); + + const currentHostnames = ingressConfig?.status?.componentRoutes?.find( + (cr) => cr.name === OAUTH_ROUTE_PREFIX && cr.namespace === OAUTH_NAMESPACE, + )?.currentHostnames; + const currentHostname = + getDomainFromPrefix(`${OAUTH_ROUTE_PREFIX}.apps.`, currentHostnames?.[0]) || domain; + + if (currentHostname) { + handleSetDomain(currentHostname); } setClean(); From 42e4855a64a19e6a309309432bb99cb1e7979d62 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 15 Jun 2022 13:43:18 +0200 Subject: [PATCH 09/33] Change Success to InProgress icon when waiting on reconciliation --- .../src/components/PersistPage/PersistPageBottom.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx b/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx index 9e7c1b281..00fe3ec1d 100644 --- a/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx +++ b/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx @@ -8,11 +8,8 @@ import { Stack, StackItem, } from '@patternfly/react-core'; -import { CheckCircleIcon, InProgressIcon } from '@patternfly/react-icons'; -import { - global_primary_color_light_100 as progressColor, - global_success_color_100 as successColor, -} from '@patternfly/react-tokens'; +import { InProgressIcon } from '@patternfly/react-icons'; +import { global_primary_color_light_100 as progressColor } from '@patternfly/react-tokens'; import { navigateToNewDomain, persist } from './persist'; import { PersistErrorType } from './types'; @@ -88,10 +85,7 @@ export const PersistPageBottom: React.FC = () => { ) : ( <> - + Settings succesfully saved, it might take several minutes for cluster to reconcile. From 046ea9cc1d011a95a4b77339311126c8bb30eca4 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Thu, 16 Jun 2022 13:19:57 +0200 Subject: [PATCH 10/33] Add custom domain certificate validation --- .../DomainCertificatesDecision.tsx | 2 +- .../DomainCertificates.tsx | 8 +- .../DomainCertificatesPanel.tsx | 98 +++++++++---------- .../SettingsPageDomainCertificates.tsx | 2 +- ui/frontend/src/components/Wizard/Wizard.css | 7 ++ ui/frontend/src/components/utils.ts | 35 +++++-- 6 files changed, 91 insertions(+), 61 deletions(-) diff --git a/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx b/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx index 5910acede..f09d75992 100644 --- a/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx +++ b/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx @@ -44,7 +44,7 @@ export const DomainCertificatesDecision: React.FC How do you want to assign certificates to your domain? - Choose whether or not you want to automatically assign self-signed certificates for your + Choose whether or not you want to automatically assign self-signed PEM certificates for your custom domain. diff --git a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx index 96056df96..65198b0fc 100644 --- a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx +++ b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificates.tsx @@ -12,9 +12,11 @@ export const DomainCertificates: React.FC = () => { Upload your certificates - - Secure your domains with SSL/TLS certificates. If a certificate is not provided, a - self-signed one will be automatically generated. + + Secure your SSL/TLS routes with certificates in the PEM format. + + + If a certificate is not provided, a self-signed one will be automatically generated. diff --git a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx index 9b62649e1..b813ae2f5 100644 --- a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx +++ b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Buffer } from 'buffer'; import { ExpandableSection, FileUpload, @@ -36,6 +35,7 @@ import { TlsCertificate, } from '../../copy-backend-common'; import { useK8SStateContext } from '../K8SStateContext'; +import { toBase64 } from '../utils'; const getTitle = ( isExpanded: boolean, @@ -101,7 +101,7 @@ const Certificate: React.FC = ({ name, domain, isSpaceItemsNon const fr = new FileReader(); fr.onload = () => { - newCert[key] = Buffer.from(fr.result as string).toString('base64'); + newCert[key] = toBase64(fr.result as string); setCustomCertificate(domain, newCert); }; fr.readAsText(file); @@ -143,54 +143,52 @@ const Certificate: React.FC = ({ name, domain, isSpaceItemsNon -
- - - - onChange('tls.crt', file)} - onClearClick={() => { - onClear('tls.crt'); - }} - /> - - - - - - onChange('tls.key', file)} - onClearClick={() => { - onClear('tls.key'); - }} - /> - - - -
+ + + + onChange('tls.crt', file)} + onClearClick={() => { + onClear('tls.crt'); + }} + /> + + + + + + onChange('tls.key', file)} + onClearClick={() => { + onClear('tls.key'); + }} + /> + + + ); }; diff --git a/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx b/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx index 7e8cf0eec..059ecffac 100644 --- a/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx +++ b/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx @@ -22,7 +22,7 @@ export const SettingsPageDomainCertificates: React.FC = () => { fieldId="automatic" label="Domain certificate assignment" isRequired={true} - helperText="Choose whether or not you want to automatically assign certificates for your cluster domain." + helperText="Choose whether or not you want to automatically assign PEM certificates for your cluster domain." > Buffer.from(str).toString('base64'); +export const fromBase64ToUtf8 = (b64Str?: string): string | undefined => + b64Str === undefined ? undefined : Buffer.from(b64Str, 'base64').toString('utf8'); + export const addIpDots = (addressWithoutDots: string): string => { if (addressWithoutDots?.length === 12) { let address = addressWithoutDots.substring(0, 3).trim() + '.'; @@ -104,12 +110,29 @@ export const customCertsValidator = ( keyLabelInvalid = 'Both key and certificate must be provided at once.'; } - if (certificate?.['tls.crt'] && certificate?.['tls.key']) { - // TODO: more in-depth content check?? - // -----BEGIN CERTIFICATE----- - // -----BEGIN PRIVATE KEY----- - certValidated = 'success'; - keyValidated = 'success'; + const tlsCrt = fromBase64ToUtf8(certificate['tls.crt'])?.trim().split('\n'); + const tlsKey = fromBase64ToUtf8(certificate['tls.key'])?.trim().split('\n'); + if (tlsCrt?.length && tlsKey?.length && tlsCrt.length > 2 && tlsKey.length > 2) { + // The header/footer are not required but commonly used, so let's try to check the format based on them + if ( + !tlsCrt[0].includes('--BEGIN CERTIFICATE--') || + !tlsCrt?.[tlsCrt.length - 1].includes('--END CERTIFICATE--') + ) { + certValidated = 'error'; + certLabelInvalid = 'The provided certificate does not conform PEM format.'; + } else { + certValidated = 'success'; + } + + if ( + !tlsKey[0].includes('--BEGIN PRIVATE KEY--') || + !tlsKey?.[tlsKey.length - 1].includes('--END PRIVATE KEY--') + ) { + keyValidated = 'error'; + keyLabelInvalid = 'The provided key does not conform PEM format.'; + } else { + keyValidated = 'success'; + } } validation[domain] = { From efd5df79ba42b9fd6b97ec55ededcf804c18f787 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Thu, 16 Jun 2022 13:59:07 +0200 Subject: [PATCH 11/33] Make Custom Certs on the SettingsPage more readable --- .../ContentTwoCols/ContentTwoCols.tsx | 10 ++-- .../DomainCertificatesPanel.tsx | 3 +- .../src/components/Settings/Settings.test.tsx | 9 ++-- .../src/components/Settings/Settings.tsx | 41 ++++++++++---- .../Settings/SettingsPageContext.tsx | 53 +++++++++++++++++++ .../SettingsPageDomainCertificates.tsx | 12 ++--- .../components/Settings/SettingsPageRight.tsx | 7 ++- 7 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 ui/frontend/src/components/Settings/SettingsPageContext.tsx diff --git a/ui/frontend/src/components/ContentTwoCols/ContentTwoCols.tsx b/ui/frontend/src/components/ContentTwoCols/ContentTwoCols.tsx index 650510c6b..87949f443 100644 --- a/ui/frontend/src/components/ContentTwoCols/ContentTwoCols.tsx +++ b/ui/frontend/src/components/ContentTwoCols/ContentTwoCols.tsx @@ -1,14 +1,16 @@ import React from 'react'; -import { Grid, GridItem } from '@patternfly/react-core'; +import { Grid, GridItem, gridSpans } from '@patternfly/react-core'; import './ContentTwoCols.css'; export const ContentTwoCols: React.FC<{ left: React.ReactNode; right: React.ReactNode; -}> = ({ left, right }) => ( + spanLeft?: gridSpans; + spanRight?: gridSpans; +}> = ({ left, right, spanLeft = 6, spanRight = 6 }) => ( - {left} - {right} + {left} + {right} ); diff --git a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx index b813ae2f5..8579bea75 100644 --- a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx +++ b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx @@ -9,7 +9,6 @@ import { FlexItem, Flex, FlexProps, - Form, DescriptionList, DescriptionListGroup, DescriptionListTerm, @@ -115,7 +114,7 @@ const Certificate: React.FC = ({ name, domain, isSpaceItemsNon }; const spaceItems: FlexProps['spaceItems'] = isSpaceItemsNone - ? { default: 'spaceItemsNone' } + ? { default: 'spaceItemsXs' } : undefined; return ( diff --git a/ui/frontend/src/components/Settings/Settings.test.tsx b/ui/frontend/src/components/Settings/Settings.test.tsx index 0bfc340ca..63416bd7a 100644 --- a/ui/frontend/src/components/Settings/Settings.test.tsx +++ b/ui/frontend/src/components/Settings/Settings.test.tsx @@ -5,6 +5,7 @@ import { MemoryRouter } from 'react-router-dom'; import { SettingsContent, SettingsLoading } from './Settings'; import { K8SStateContextProvider, useK8SStateContext } from '../K8SStateContext'; import { ipWithoutDots } from '../utils'; +import { SettingsPageContextProvider } from './SettingsPageContext'; type CTX_TYPE = { ctxData?: { apiaddr: string; ingressIp: string; domain: string }; @@ -26,9 +27,11 @@ const TestedComponent: React.FC = ({ ctxData, error }) => { const Component: React.FC = (props) => ( - - - + + + + + ); diff --git a/ui/frontend/src/components/Settings/Settings.tsx b/ui/frontend/src/components/Settings/Settings.tsx index 11ed79110..b0ad58ddf 100644 --- a/ui/frontend/src/components/Settings/Settings.tsx +++ b/ui/frontend/src/components/Settings/Settings.tsx @@ -8,20 +8,37 @@ import { initialDataLoad } from '../WelcomePage/initialDataLoad'; import { Spinner } from './Spinner'; import { SettingsPageLeft } from './SettingsPageLeft'; import { SettingsPageRight } from './SettingsPageRight'; +import { gridSpans } from '@patternfly/react-core'; +import { SettingsPageContextProvider, useSettingsPageContext } from './SettingsPageContext'; export const SettingsContent: React.FC<{ error?: string; forceReload: () => void }> = ({ error, forceReload, -}) => ( - - } - right={ - - } - /> - -); +}) => { + const ctx = useSettingsPageContext(); + + const getSettingsSpan = (): { spanLeft: gridSpans; spanRight: gridSpans } => { + let spanLeft: gridSpans = 6; + let spanRight: gridSpans = 6; + + if (ctx.isEdit && ctx.activeTabKey === 1 /* Domain */ && !ctx.isCertificateAutomatic) { + spanLeft = 2; + spanRight = 10; + } + + return { spanLeft, spanRight }; + }; + + return ( + + } + right={} + {...getSettingsSpan()} + /> + + ); +}; export const SettingsLoading: React.FC = () => ( @@ -52,7 +69,9 @@ export const Settings: React.FC = () => { }, [handleSetApiaddr, handleSetIngressIp, handleSetDomain, isReload, setClean]); return isDataLoaded ? ( - setReload(true)} /> + + setReload(true)} /> + ) : ( ); diff --git a/ui/frontend/src/components/Settings/SettingsPageContext.tsx b/ui/frontend/src/components/Settings/SettingsPageContext.tsx new file mode 100644 index 000000000..ab5d5fc8c --- /dev/null +++ b/ui/frontend/src/components/Settings/SettingsPageContext.tsx @@ -0,0 +1,53 @@ +import React from 'react'; + +export type SettingsPageContextData = { + isEdit: boolean; + setEdit: (v: boolean) => void; + + activeTabKey: number; + setActiveTabKey: (v: number) => void; + + isCertificateAutomatic: boolean; + setCertificateAutomatic: (v: boolean) => void; +}; + +const SettingsPageContext = React.createContext(undefined); + +export const SettingsPageContextProvider: React.FC<{ + children: React.ReactNode; +}> = ({ children }) => { + const [isEdit, setEdit] = React.useState(false); + const [activeTabKey, setActiveTabKey] = React.useState(0); + const [isCertificateAutomatic, setCertificateAutomatic] = React.useState(true); + + const value = React.useMemo( + () => ({ + isEdit, + setEdit, + + activeTabKey, + setActiveTabKey, + + isCertificateAutomatic, + setCertificateAutomatic, + }), + [ + activeTabKey, + setActiveTabKey, + isCertificateAutomatic, + setCertificateAutomatic, + isEdit, + setEdit, + ], + ); + + return {children}; +}; + +export const useSettingsPageContext = () => { + const context = React.useContext(SettingsPageContext); + if (!context) { + throw new Error('useSettingsPageContext must be used within K8SSettingsPageContextProvider.'); + } + return context; +}; diff --git a/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx b/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx index 059ecffac..b54637591 100644 --- a/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx +++ b/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx @@ -2,10 +2,10 @@ import { FormGroup, StackItem } from '@patternfly/react-core'; import React from 'react'; import { AutomaticManualDecision } from '../DomainCertificatesDecisionPage/DomainCertificatesDecision'; import { DomainCertificatesPanel } from '../DomainCertificatesPage/DomainCertificatesPanel'; +import { useSettingsPageContext } from './SettingsPageContext'; export const SettingsPageDomainCertificates: React.FC = () => { - const [isAutomatic, setAutomatic] = React.useState(true); - + const { isCertificateAutomatic, setCertificateAutomatic } = useSettingsPageContext(); React.useEffect( () => { /* TODO: decide about isutomatic */ @@ -25,15 +25,15 @@ export const SettingsPageDomainCertificates: React.FC = () => { helperText="Choose whether or not you want to automatically assign PEM certificates for your cluster domain." >
- {!isAutomatic && ( + {!isCertificateAutomatic && ( - + )} diff --git a/ui/frontend/src/components/Settings/SettingsPageRight.tsx b/ui/frontend/src/components/Settings/SettingsPageRight.tsx index 5f17d9b07..66fc7219c 100644 --- a/ui/frontend/src/components/Settings/SettingsPageRight.tsx +++ b/ui/frontend/src/components/Settings/SettingsPageRight.tsx @@ -21,18 +21,17 @@ import { useK8SStateContext } from '../K8SStateContext'; import { DeleteKubeadminButton } from './DeleteKubeadminButton'; import { PersistProgress, usePersistProgress } from '../PersistProgress'; import { SettingsPageDomainCertificates } from './SettingsPageDomainCertificates'; +import { useSettingsPageContext } from './SettingsPageContext'; import './SettingsPageRight.css'; export const SettingsPageRight: React.FC<{ - isInitialEdit?: boolean; initialError?: string; forceReload: () => void; -}> = ({ isInitialEdit, initialError, forceReload }) => { - const [isEdit, setEdit] = React.useState(isInitialEdit); +}> = ({ initialError, forceReload }) => { const [isSaving, setIsSaving] = React.useState(false); const [_error, setError] = React.useState(); - const [activeTabKey, setActiveTabKey] = React.useState(0); + const { activeTabKey, setActiveTabKey, isEdit, setEdit } = useSettingsPageContext(); const state = useK8SStateContext(); const progress = usePersistProgress(); From 786ea7aa7f13af94b68df5761f6ff4cf27e9817a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Iranzo=20G=C3=B3mez?= Date: Fri, 17 Jun 2022 10:57:15 +0200 Subject: [PATCH 12/33] ci(Makefile): Patch previously the finalizers before managedcluster removal --- .pre-commit-config.yaml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 48cf1804e..ccd89eb43 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - files: \.(css|js|md|markdown|json) id: prettier repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.0 + rev: v2.7.1 - hooks: - id: seed-isort-config repo: https://github.com/asottile/seed-isort-config diff --git a/Makefile b/Makefile index 935efde4d..0f0ae7b09 100644 --- a/Makefile +++ b/Makefile @@ -193,10 +193,10 @@ clean-ci: kcli delete vm -y $(EDGE_NAME)-m0 $(EDGE_NAME)-m1 $(EDGE_NAME)-m2 $(EDGE_NAME)-w0; \ list=$$(tkn pr ls -n edgecluster-deployer |grep -i running | cut -d' ' -f1); \ for i in ${list}; do tkn pr cancel $${i} -n edgecluster-deployer; done; \ - oc delete --ignore-not-found=true managedcluster $(EDGE_NAME); \ list=$$($ oc get bmh -n $(EDGE_NAME) --no-headers|awk '{print $$1}'); \ for i in $${list}; do oc patch -n $(EDGE_NAME) bmh $${i} --type json -p '[ { "op": "remove", "path": "/metadata/finalizers" } ]'; done; \ list=$$(oc get secret -n $(EDGE_NAME) --no-headers |grep bmc|awk '{print $$1}'); \ for i in $${list}; do oc patch -n $(EDGE_NAME) secret $${i} --type json -p '[ { "op": "remove", "path": "/metadata/finalizers" } ]'; done; \ + oc delete --ignore-not-found=true managedcluster $(EDGE_NAME); \ oc delete --ignore-not-found=true ns $(EDGE_NAME); \ oc rollout restart -n openshift-machine-api deployment/metal3; From e1d351e7f37805fa02cd3c9837afbeb8e3f9c231 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Fri, 17 Jun 2022 10:33:56 +0200 Subject: [PATCH 13/33] Fix ZTPFW route change for new domain --- ui/backend/src/common/resources/ingress.ts | 19 +++++++++++ ui/backend/src/endpoints/changeDomain.ts | 32 ++++++------------- .../src/components/K8SStateContext.tsx | 7 ++-- .../components/Settings/SettingsPageRight.tsx | 11 +++++-- .../__snapshots__/Settings.test.tsx.snap | 8 ++--- .../components/WelcomePage/initialDataLoad.ts | 30 ++++------------- 6 files changed, 51 insertions(+), 56 deletions(-) diff --git a/ui/backend/src/common/resources/ingress.ts b/ui/backend/src/common/resources/ingress.ts index 27a684178..04b8c25aa 100644 --- a/ui/backend/src/common/resources/ingress.ts +++ b/ui/backend/src/common/resources/ingress.ts @@ -1,3 +1,4 @@ +import { OAUTH_NAMESPACE, OAUTH_ROUTE_PREFIX } from '../domains'; import { Metadata } from './metadata'; import { IResource } from './resource'; @@ -35,3 +36,21 @@ export interface Ingress extends IResource { }[]; }; } + +export const getDomainFromPrefix = (prefix: string, domain?: string) => { + let result = domain?.trim(); + if (result?.startsWith(prefix)) { + result = result.substring(prefix.length); + } + return result; +}; + +export const getClusterDomainFromComponentRoutes = (ingress?: Ingress) => { + const defaultDomain = getDomainFromPrefix('apps.', ingress?.spec?.domain); + + const currentHostnames = ingress?.status?.componentRoutes?.find( + (cr) => cr.name === OAUTH_ROUTE_PREFIX && cr.namespace === OAUTH_NAMESPACE, + )?.currentHostnames; + + return getDomainFromPrefix(`${OAUTH_ROUTE_PREFIX}.apps.`, currentHostnames?.[0]) || defaultDomain; +}; diff --git a/ui/backend/src/endpoints/changeDomain.ts b/ui/backend/src/endpoints/changeDomain.ts index 74de3be6c..02e7ea513 100644 --- a/ui/backend/src/endpoints/changeDomain.ts +++ b/ui/backend/src/endpoints/changeDomain.ts @@ -11,6 +11,7 @@ import { getZtpfwDomain, ZTPFW_UI_ROUTE_PREFIX, OAUTH_NAMESPACE, + getClusterDomainFromComponentRoutes, } from '../common'; import { ZTPFW_DEPLOYMENT_NAME, @@ -102,22 +103,20 @@ const updateIngressComponentRoutes = async ( const updateSingleRoute = async ( token: string, - oldIngressDomain: string, - ingressDomain: string, + ztpfwDomain: string, route: Route, ): Promise | void> => { if (route.spec?.host) { - const newHost = route.spec.host.replace(oldIngressDomain, ingressDomain); - if (newHost === route.spec.host) { + if (ztpfwDomain === route.spec.host) { logger.debug( - `No change for the ${route.metadata.namespace}/${route.metadata.name} route, keeping host: "${newHost}".`, + `No change for the ${route.metadata.namespace}/${route.metadata.name} route, keeping host: "${route.spec.host}".`, ); } else { const patch: PatchType[] = [ { op: 'replace', path: '/spec/host', - value: newHost, + value: ztpfwDomain, }, ]; @@ -128,7 +127,7 @@ const updateSingleRoute = async ( patch, ).then((r) => { logger.debug( - `Route ${route.metadata.namespace}/${route.metadata.name} is patched, new host: ${newHost}`, + `Route ${route.metadata.namespace}/${route.metadata.name} is patched, new host: ${ztpfwDomain}`, ); return r; }); @@ -233,13 +232,7 @@ const updateZtpfwDeployment = async ( } }; -const updateZtpfwUI = async ( - token: string, - // ztpfwUiTlsSecretName: string, - ztpfwDomain: string, - ingressDomain: string, - oldIngressDomain = '', -) => { +const updateZtpfwUI = async (token: string, ztpfwDomain: string) => { // route try { const route = await getRoute(token, { name: ZTPFW_ROUTE_NAME, namespace: ZTPFW_NAMESPACE }); @@ -247,7 +240,7 @@ const updateZtpfwUI = async ( // Make a copy to be able to make livenessProbe requests from browser (new route hots CORS issue) await backupRoute(token, route); - await updateSingleRoute(token, oldIngressDomain, ingressDomain, route); + await updateSingleRoute(token, ztpfwDomain, route); logger.debug('ZTPFW UI Route patched'); } catch (e) { logger.error('Failed to patch ZTPFW UI Route: ', e); @@ -323,7 +316,7 @@ const changeDomainImpl = async ( const ingress = await getIngressConfig(token); - const oldIngressDomain = ingress.spec?.domain; + const oldIngressDomain = getIngressDomain(getClusterDomainFromComponentRoutes(ingress) || ''); const apiDomain = getApiDomain(clusterDomain); const ingressDomain = getIngressDomain(clusterDomain); @@ -449,12 +442,7 @@ const changeDomainImpl = async ( } // This will terminate our pod - await updateZtpfwUI( - token, - /*ztpfwUiTlsSecretName,*/ ztpfwDomain, - ingressDomain, - oldIngressDomain, - ); + await updateZtpfwUI(token, ztpfwDomain); res.writeHead(200).end(); // All good }; diff --git a/ui/frontend/src/components/K8SStateContext.tsx b/ui/frontend/src/components/K8SStateContext.tsx index 88b077221..f7ebc416b 100644 --- a/ui/frontend/src/components/K8SStateContext.tsx +++ b/ui/frontend/src/components/K8SStateContext.tsx @@ -118,10 +118,9 @@ export const K8SStateContextProvider: React.FC<{ const setClean = React.useCallback(() => { setSnapshot(fieldValues); }, [fieldValues]); - const isDirty = React.useCallback( - (): boolean => !isEqual(fieldValues, snapshot), - [fieldValues, snapshot], - ); + const isDirty = React.useCallback((): boolean => { + return !isEqual(fieldValues, snapshot); + }, [fieldValues, snapshot]); const value = React.useMemo( () => ({ diff --git a/ui/frontend/src/components/Settings/SettingsPageRight.tsx b/ui/frontend/src/components/Settings/SettingsPageRight.tsx index 66fc7219c..23cf378b4 100644 --- a/ui/frontend/src/components/Settings/SettingsPageRight.tsx +++ b/ui/frontend/src/components/Settings/SettingsPageRight.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { FormEvent } from 'react'; import { Alert, AlertVariant, @@ -83,6 +83,11 @@ export const SettingsPageRight: React.FC<{ forceReload(); }; + const onFormSubmit = (e: FormEvent) => { + e.preventDefault(); + console.log('onFormSubmit() called, ignoring, click on Save instead'); + }; + const isSaveDisabled = isSaving || !isDirty(); return ( @@ -101,7 +106,7 @@ export const SettingsPageRight: React.FC<{ TCP/IP}> -
+ Domain}> - +
{ - let result = domain?.trim(); - if (result?.startsWith(prefix)) { - result = result.substring(prefix.length); - } - return result; -}; +import { getClusterDomainFromComponentRoutes, Ingress } from '../../copy-backend-common'; export const initialDataLoad = async ({ setNextPage, @@ -26,7 +18,7 @@ export const initialDataLoad = async ({ handleSetDomain, setClean, }: { - setNextPage?: (href: string) => void; + setNextPage: (href: string) => void; setError: (message?: string) => void; handleSetApiaddr: K8SStateContextData['handleSetApiaddr']; handleSetIngressIp: K8SStateContextData['handleSetIngressIp']; @@ -76,14 +68,7 @@ export const initialDataLoad = async ({ ), ); - const domain = getDomainFromPrefix('apps.', ingressConfig?.spec?.domain); - - const currentHostnames = ingressConfig?.status?.componentRoutes?.find( - (cr) => cr.name === OAUTH_ROUTE_PREFIX && cr.namespace === OAUTH_NAMESPACE, - )?.currentHostnames; - const currentHostname = - getDomainFromPrefix(`${OAUTH_ROUTE_PREFIX}.apps.`, currentHostnames?.[0]) || domain; - + const currentHostname = getClusterDomainFromComponentRoutes(ingressConfig); if (currentHostname) { handleSetDomain(currentHostname); } @@ -92,10 +77,9 @@ export const initialDataLoad = async ({ if (getHtpasswdIdentityProvider(oauth)) { // The Edit flow for the 2nd and later run - setNextPage && setNextPage('/settings'); - return; + setNextPage('/settings'); + } else { + // The Wizard for the very first run + setNextPage('/wizard/username'); } - - // The Wizard for the very first run - setNextPage && setNextPage('/wizard/username'); }; From 917c29e44fcb84de0e825125e17908e0e0c4a1dd Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Fri, 17 Jun 2022 11:07:05 +0200 Subject: [PATCH 14/33] MGMT-10843 Rephrase SettingsPageDomainCertificates screen --- .../DomainCertificatesDecision.tsx | 4 ++-- .../components/Settings/SettingsPageDomainCertificates.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx b/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx index f09d75992..c03420497 100644 --- a/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx +++ b/ui/frontend/src/components/DomainCertificatesDecisionPage/DomainCertificatesDecision.tsx @@ -44,8 +44,8 @@ export const DomainCertificatesDecision: React.FC How do you want to assign certificates to your domain? - Choose whether or not you want to automatically assign self-signed PEM certificates for your - custom domain. + Choose whether you want to automatically generate and assign self-signed PEM certificates + for your custom domain. diff --git a/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx b/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx index b54637591..9556ae497 100644 --- a/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx +++ b/ui/frontend/src/components/Settings/SettingsPageDomainCertificates.tsx @@ -22,7 +22,7 @@ export const SettingsPageDomainCertificates: React.FC = () => { fieldId="automatic" label="Domain certificate assignment" isRequired={true} - helperText="Choose whether or not you want to automatically assign PEM certificates for your cluster domain." + helperText="Choose whether you want to automatically generate and assign PEM certificates for your cluster domain." > Date: Fri, 17 Jun 2022 11:20:18 +0200 Subject: [PATCH 15/33] MGMT-10845 Fix expandable title for incorrect certificate --- .../DomainCertificatesPanel.tsx | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx index 8579bea75..16b95d619 100644 --- a/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx +++ b/ui/frontend/src/components/DomainCertificatesPage/DomainCertificatesPanel.tsx @@ -14,6 +14,7 @@ import { DescriptionListTerm, DescriptionListDescription, ClipboardCopy, + FormGroupProps, } from '@patternfly/react-core'; import { global_success_color_100 as successColor, @@ -41,21 +42,29 @@ const getTitle = ( domainCert: TlsCertificate, name: string, domain: string, + certValidated: FormGroupProps['validated'], + keyValidated: FormGroupProps['validated'], ): React.ReactElement | string => { let title: React.ReactElement | string; const forDomain = isExpanded ? undefined : <> for {domain}; - if (domainCert?.['tls.crt'] && domainCert?.['tls.key']) { + if (!domainCert?.['tls.crt'] && !domainCert?.['tls.key']) { title = ( <> - {name}: done {forDomain} + {name}: generate self-signed{' '} + {forDomain} ); - } else if (!domainCert?.['tls.crt'] && !domainCert?.['tls.key']) { + } else if (certValidated === 'error' || keyValidated === 'error') { title = ( <> - {name}: generate self-signed{' '} - {forDomain} + {name}: incorrect {forDomain} + + ); + } else if (domainCert?.['tls.crt'] && domainCert?.['tls.key']) { + title = ( + <> + {name}: done {forDomain} ); } else { @@ -125,6 +134,8 @@ const Certificate: React.FC = ({ name, domain, isSpaceItemsNon domainCert, name, domain, + certValidated, + keyValidated, ) as unknown as string /* TODO: Add support to Patternfly to avoid this ugly re-typying hack. It worsk so far. */ } onToggle={() => setExpanded(!isExpanded)} From 63d6835cbcd22cc5e0f51e8887adceb7b435149a Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Fri, 17 Jun 2022 13:28:11 +0200 Subject: [PATCH 16/33] MGMT-10846 Disable Save button if form is invalid --- .../src/components/K8SStateContext.tsx | 24 +++++++++++++++++++ .../components/Settings/SettingsPageRight.tsx | 4 ++-- ui/frontend/src/components/types.ts | 1 + 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/ui/frontend/src/components/K8SStateContext.tsx b/ui/frontend/src/components/K8SStateContext.tsx index f7ebc416b..017a8b2f0 100644 --- a/ui/frontend/src/components/K8SStateContext.tsx +++ b/ui/frontend/src/components/K8SStateContext.tsx @@ -101,6 +101,28 @@ export const K8SStateContextProvider: React.FC<{ [customCertsValidation, customCerts, setCustomCerts], ); + const isAllValid = React.useCallback(() => { + const result = + !usernameValidation && + passwordValidation && + apiaddrValidation.valid && + ingressIpValidation.valid && + !domainValidation && + !Object.keys(customCertsValidation).find( + (d) => + customCertsValidation[d].certValidated === 'error' || + customCertsValidation[d].keyValidated === 'error', + ); + return result; + }, [ + apiaddrValidation.valid, + customCertsValidation, + domainValidation, + ingressIpValidation.valid, + passwordValidation, + usernameValidation, + ]); + const fieldValues: K8SStateContextDataFields = React.useMemo( () => ({ username, @@ -128,6 +150,7 @@ export const K8SStateContextProvider: React.FC<{ isDirty, setClean, + isAllValid, usernameValidation, handleSetUsername, @@ -151,6 +174,7 @@ export const K8SStateContextProvider: React.FC<{ fieldValues, isDirty, setClean, + isAllValid, usernameValidation, handleSetUsername, passwordValidation, diff --git a/ui/frontend/src/components/Settings/SettingsPageRight.tsx b/ui/frontend/src/components/Settings/SettingsPageRight.tsx index 23cf378b4..04944eba6 100644 --- a/ui/frontend/src/components/Settings/SettingsPageRight.tsx +++ b/ui/frontend/src/components/Settings/SettingsPageRight.tsx @@ -45,6 +45,7 @@ export const SettingsPageRight: React.FC<{ const { isDirty, setClean, + isAllValid, apiaddr, apiaddrValidation, @@ -77,6 +78,7 @@ export const SettingsPageRight: React.FC<{ const isAfterRedirection = window.location.hash === '#redirected'; const isDomainChange = originalDomain && originalDomain !== domain; + const isSaveDisabled = isSaving || !isAllValid() || !isDirty(); const onCancelEdit = () => { setEdit(false); @@ -88,8 +90,6 @@ export const SettingsPageRight: React.FC<{ console.log('onFormSubmit() called, ignoring, click on Save instead'); }; - const isSaveDisabled = isSaving || !isDirty(); - return ( diff --git a/ui/frontend/src/components/types.ts b/ui/frontend/src/components/types.ts index 83d7f09a3..bfbb95a47 100644 --- a/ui/frontend/src/components/types.ts +++ b/ui/frontend/src/components/types.ts @@ -45,6 +45,7 @@ export type K8SStateContextDataFields = { export type K8SStateContextData = K8SStateContextDataFields & { isDirty: () => boolean; setClean: () => void; + isAllValid: () => boolean; usernameValidation?: string; // just a message or empty handleSetUsername: (newVal: string) => void; From 70ece87369711aa19e5367bdf2b728818348809a Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 20 Jun 2022 08:46:19 +0200 Subject: [PATCH 17/33] Log git-hash in the Browser's console log --- ui/frontend/package.json | 3 ++- ui/frontend/src/index.tsx | 2 ++ ui/frontend/src/sha.ts | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 ui/frontend/src/sha.ts diff --git a/ui/frontend/package.json b/ui/frontend/package.json index 0857c41cc..36b6ea519 100644 --- a/ui/frontend/package.json +++ b/ui/frontend/package.json @@ -37,7 +37,8 @@ "start": "HTTPS=true SSL_CRT_FILE=${TLS_CERT_FILE} SSL_KEY_FILE=${TLS_KEY_FILE} react-scripts start", "build": "react-scripts build", "backend-common": "rm -rf src/copy-backend-common ; cp -r ../backend/src/common src/copy-backend-common", - "prebuild": "yarn backend-common", + "get-sha": "echo \"GIT_BUILD_SHA = '$(git rev-parse HEAD)';\" >> src/sha.ts", + "prebuild": "yarn backend-common ; yarn get-sha", "prestart": "yarn backend-common", "pretest": "yarn backend-common", "test": "yarn test:circular && react-scripts test", diff --git a/ui/frontend/src/index.tsx b/ui/frontend/src/index.tsx index 5604a6435..84332431c 100644 --- a/ui/frontend/src/index.tsx +++ b/ui/frontend/src/index.tsx @@ -5,6 +5,7 @@ import ReactDOM from 'react-dom'; import App from './App'; import { getBackendUrl } from './resources'; +import { GIT_BUILD_SHA } from './sha'; import './index.css'; @@ -21,5 +22,6 @@ ReactDOM.render( // reportWebVitals(); // For debugging - especially after domain change +console.info('***** The Edgecluster UI version: ', GIT_BUILD_SHA); console.log('UI Backend URL: ', getBackendUrl()); console.log('Frontend accessed at: ', window.location.href); diff --git a/ui/frontend/src/sha.ts b/ui/frontend/src/sha.ts new file mode 100644 index 000000000..815b3df79 --- /dev/null +++ b/ui/frontend/src/sha.ts @@ -0,0 +1,5 @@ +// eslint-disable-next-line prefer-const +export let GIT_BUILD_SHA = ''; +// GIT_BUILD_SHA will be changed at build time + +/* DO NOT COMMIT CHANGES BELOW THIS LINE, THEY ARE ADDED AUTOMATICALLY */ From 419e1b5c3aa929359a75bc76fde700c00053b6ae Mon Sep 17 00:00:00 2001 From: Eran Ifrach <76165038+eifrach@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:52:34 +0300 Subject: [PATCH 18/33] Ci Bootstrap before pipeline --- .github/workflows/testing-pr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/testing-pr.yml b/.github/workflows/testing-pr.yml index 132092477..0e014266a 100644 --- a/.github/workflows/testing-pr.yml +++ b/.github/workflows/testing-pr.yml @@ -122,6 +122,7 @@ jobs: echo ">>>>" RELEASE=${RELEASE} make build-edgecluster-compact + BRANCH=${{ github.event.pull_request.head.ref }} make bootstrap RELEASE=${RELEASE} make deploy-pipe-edgecluster-compact-ci - name: verify if the pipe has been successful From a25a83bbac39e85110eb2ad7965c6ab3e56051af Mon Sep 17 00:00:00 2001 From: Eran Ifrach <76165038+eifrach@users.noreply.github.com> Date: Mon, 20 Jun 2022 10:01:31 +0300 Subject: [PATCH 19/33] Update testing-pr.yml --- .github/workflows/testing-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing-pr.yml b/.github/workflows/testing-pr.yml index 0e014266a..4872b54ea 100644 --- a/.github/workflows/testing-pr.yml +++ b/.github/workflows/testing-pr.yml @@ -121,8 +121,8 @@ jobs: echo "Git hash: ${{ github.sha }}" echo ">>>>" - RELEASE=${RELEASE} make build-edgecluster-compact BRANCH=${{ github.event.pull_request.head.ref }} make bootstrap + RELEASE=${RELEASE} make build-edgecluster-compact RELEASE=${RELEASE} make deploy-pipe-edgecluster-compact-ci - name: verify if the pipe has been successful From 94a4e8335a2f51ed2f35ff600c70c82e3e6a98db Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 20 Jun 2022 11:21:01 +0200 Subject: [PATCH 20/33] MGMT-10851 Add blocking prior IP change --- .../__snapshots__/PersistPage.test.tsx.snap | 6 +++--- ui/frontend/src/components/PersistPage/persist.ts | 12 +++++++++++- .../components/PersistProgress/PersistProgress.tsx | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap b/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap index 2e699b3c2..295f630ff 100644 --- a/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap +++ b/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap @@ -155,20 +155,20 @@ exports[`PersistPage can render 1`] = ` - Waiting for the configuration pod + Saving Ingress IP
Date: Mon, 20 Jun 2022 12:29:26 +0200 Subject: [PATCH 21/33] Move getting UI git sha to Dockerfile --- images/Containerfile.UI | 5 +++++ ui/frontend/package.json | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/images/Containerfile.UI b/images/Containerfile.UI index 6b4ff69a5..63ad7eed8 100644 --- a/images/Containerfile.UI +++ b/images/Containerfile.UI @@ -11,10 +11,15 @@ RUN curl -L --output /yarn.rpm $(cat /yarn.url) RUN dnf install -y /yarn.rpm WORKDIR /app + COPY ./ui/frontend ./frontend COPY ./ui/backend ./backend COPY ./ui/package.json ./ui/yarn.lock ./ +# just to get latest SHA at build time +COPY ./.git ./.git +RUN cd ./frontend ; yarn get-sha ; cd ../ ; rm -rf ./git + RUN yarn clean # Reduce flakiness. The NPM registry network operations can fail sometime ... RUN yarn install || (sleep 5 ; yarn install) || (sleep 5 ; yarn install) diff --git a/ui/frontend/package.json b/ui/frontend/package.json index 36b6ea519..7d148068f 100644 --- a/ui/frontend/package.json +++ b/ui/frontend/package.json @@ -37,8 +37,8 @@ "start": "HTTPS=true SSL_CRT_FILE=${TLS_CERT_FILE} SSL_KEY_FILE=${TLS_KEY_FILE} react-scripts start", "build": "react-scripts build", "backend-common": "rm -rf src/copy-backend-common ; cp -r ../backend/src/common src/copy-backend-common", - "get-sha": "echo \"GIT_BUILD_SHA = '$(git rev-parse HEAD)';\" >> src/sha.ts", - "prebuild": "yarn backend-common ; yarn get-sha", + "get-sha": "SHA=$(git rev-parse HEAD) ; echo ${SHA} ; echo \"GIT_BUILD_SHA = '${SHA}';\" >> src/sha.ts", + "prebuild": "yarn backend-common", "prestart": "yarn backend-common", "pretest": "yarn backend-common", "test": "yarn test:circular && react-scripts test", From 6b1d1a152952f57d684c2bf5dcdf6b171f89b9d5 Mon Sep 17 00:00:00 2001 From: Eran Ifrach <76165038+eifrach@users.noreply.github.com> Date: Mon, 20 Jun 2022 15:56:31 +0300 Subject: [PATCH 22/33] MGMT-10030 Allow using a third-party registry (#421) --- deploy-disconnected-registry/common.sh | 23 +++++++++----- deploy-disconnected-registry/deploy.sh | 30 ++++++++++++++----- deploy-disconnected-registry/ocp-sync.sh | 22 +++++++++----- deploy-disconnected-registry/olm-sync.sh | 20 ++++++------- .../update-global-pullsecret.sh | 2 +- deploy-disconnected-registry/verify.sh | 17 +++++++---- .../verify_ocp_sync.sh | 3 +- .../verify_olm_sync.sh | 4 +-- deploy-edgecluster/configure_disconnected.sh | 2 +- deploy-hub-configs/deploy.sh | 19 ++++++++---- examples/config.yaml | 2 ++ images/Containerfile.pipeline | 2 +- shared-utils/common.sh | 23 ++++++++++++++ 13 files changed, 119 insertions(+), 50 deletions(-) diff --git a/deploy-disconnected-registry/common.sh b/deploy-disconnected-registry/common.sh index ac96f3836..398c33c48 100755 --- a/deploy-disconnected-registry/common.sh +++ b/deploy-disconnected-registry/common.sh @@ -75,16 +75,22 @@ function trust_internal_registry() { MYREGISTRY=$(oc --kubeconfig=${KBKNFG} get route -n ztpfw-registry ztpfw-registry-quay -o jsonpath='{.spec.host}') fi + export PATH_CA_CERT="/etc/pki/ca-trust/source/anchors/internal-registry-${clus}.crt" echo ">>>> Trusting internal registry: ${1}" echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" echo ">> Kubeconfig: ${KBKNFG}" echo ">> Mode: ${1}" echo ">> Cluster: ${clus}" - ## Update trusted CA from Helper - #TODO after sync pull secret global because crictl can't use flags and uses the generic with https://access.redhat.com/solutions/4902871 - export CA_CERT_DATA=$(oc --kubeconfig=${KBKNFG} get secret -n openshift-ingress router-certs-default -o go-template='{{index .data "tls.crt"}}') - export PATH_CA_CERT="/etc/pki/ca-trust/source/anchors/internal-registry-${clus}.crt" - echo ">> Cert: ${PATH_CA_CERT}" + + if [[ ${CUSTOM_REGISTRY} == "false" ]]; then + ## Update trusted CA from Helper + #TODO after sync pull secret global because crictl can't use flags and uses the generic with https://access.redhat.com/solutions/4902871 + export CA_CERT_DATA=$(oc --kubeconfig=${KBKNFG} get secret -n openshift-ingress router-certs-default -o go-template='{{index .data "tls.crt"}}') + echo ">> Cert: ${PATH_CA_CERT}" + else + export CA_CERT_DATA=$(openssl s_client -connect ${LOCAL_REG} -showcerts "${PATH_CA_CERT}" @@ -116,7 +122,6 @@ source ${WORKDIR}/shared-utils/common.sh echo ">>>> Get the pull secret from hub to file pull-secret" echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" -export REGISTRY=ztpfw-registry export AUTH_SECRET=../${SHARED_DIR}/htpasswd export REGISTRY_MANIFESTS=manifests export QUAY_MANIFESTS=quay-manifests @@ -139,7 +144,11 @@ if [[ ${1} == "hub" ]]; then export SOURCE_REGISTRY="quay.io" export SOURCE_INDEX="registry.redhat.io/redhat/redhat-operator-index:v${OC_OCP_VERSION_MIN}" export CERTIFIED_SOURCE_INDEX="registry.redhat.io/redhat/certified-operator-index:v${OC_OCP_VERSION_MIN}" - export DESTINATION_REGISTRY="$(oc --kubeconfig=${KUBECONFIG_HUB} get route -n ${REGISTRY} ${REGISTRY} -o jsonpath={'.status.ingress[0].host'})" + if [[ ${CUSTOM_REGISTRY} == "false" ]]; then + export DESTINATION_REGISTRY="$(oc --kubeconfig=${KUBECONFIG_HUB} get route -n ${REGISTRY} ${REGISTRY} -o jsonpath={'.status.ingress[0].host'})" + else + export DESTINATION_REGISTRY=${LOCAL_REG} + fi # OLM ## NS where the OLM images will be mirrored export OLM_DESTINATION_REGISTRY_IMAGE_NS=olm diff --git a/deploy-disconnected-registry/deploy.sh b/deploy-disconnected-registry/deploy.sh index 52b56782f..3e9b4ae6d 100755 --- a/deploy-disconnected-registry/deploy.sh +++ b/deploy-disconnected-registry/deploy.sh @@ -239,16 +239,30 @@ function deploy_registry() { } +function custom_registry() { + trust_internal_registry 'hub' + check_mcp 'hub' + render_file manifests/machine-config-certs-worker.yaml 'hub' + render_file manifests/machine-config-certs-master.yaml 'hub' + check_resource "mcp" "master" "Updated" "default" "${KUBECONFIG_HUB}" + +} + if [[ ${1} == 'hub' ]]; then + if ! ./verify.sh 'hub'; then - deploy_registry 'hub' - trust_internal_registry 'hub' - check_resource "deployment" "${REGISTRY}" "Available" "${REGISTRY}" "${KUBECONFIG_HUB}" - check_mcp 'hub' - render_file manifests/machine-config-certs-master.yaml 'hub' - render_file manifests/machine-config-certs-worker.yaml 'hub' - check_resource "mcp" "master" "Updated" "default" "${KUBECONFIG_HUB}" - check_resource "deployment" "${REGISTRY}" "Available" "${REGISTRY}" "${KUBECONFIG_HUB}" + if [[ ${CUSTOM_REGISTRY} == "false" ]]; then + deploy_registry 'hub' + trust_internal_registry 'hub' + check_resource "deployment" "${REGISTRY}" "Available" "${REGISTRY}" "${KUBECONFIG_HUB}" + check_mcp 'hub' + render_file manifests/machine-config-certs-worker.yaml 'hub' + render_file manifests/machine-config-certs-master.yaml 'hub' + check_resource "mcp" "master" "Updated" "default" "${KUBECONFIG_HUB}" + check_resource "deployment" "${REGISTRY}" "Available" "${REGISTRY}" "${KUBECONFIG_HUB}" + else + custom_registry 'hub' + fi else echo ">>>> This step to deploy registry on Hub is not neccesary, everything looks ready" fi diff --git a/deploy-disconnected-registry/ocp-sync.sh b/deploy-disconnected-registry/ocp-sync.sh index 51646e3d5..e701da49f 100755 --- a/deploy-disconnected-registry/ocp-sync.sh +++ b/deploy-disconnected-registry/ocp-sync.sh @@ -42,10 +42,15 @@ function mirror_ocp() { echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>" echo - ####### WORKAROUND: Newer versions of podman/buildah try to set overlayfs mount options when - ####### using the vfs driver, and this causes errors. - export STORAGE_DRIVER=vfs - sed -i '/^mountopt =.*/d' /etc/containers/storage.conf + if [[ ${CUSTOM_REGISTRY} == "true" ]]; then + echo "Checking Private registry creds" + if [[ ! $( podman login ${LOCAL_REG} --authfile ${PULL_SECRET}) ]]; then + echo "ERROR: Failed to login to ${LOCAL_REG}, please check Pull Secret" + exit 1 + else + echo "Login successfully to ${LOCAL_REG}" + fi + fi ####### # Empty log file @@ -82,10 +87,13 @@ if [[ ${1} == 'hub' ]]; then trust_internal_registry 'hub' if ! ./verify_ocp_sync.sh 'hub'; then - oc create namespace ${REGISTRY} -o yaml --dry-run=client | oc apply -f - - export REGISTRY_NAME="$(oc get route -n ${REGISTRY} ${REGISTRY} -o jsonpath={'.status.ingress[0].host'})" - ${PODMAN_LOGIN_CMD} ${DESTINATION_REGISTRY} -u ${REG_US} -p ${REG_PASS} --authfile=${PULL_SECRET} # to create a merge with the registry original adding the registry auth entry + if [[ ${CUSTOM_REGISTRY} == "false" ]]; then + oc create namespace ${REGISTRY} -o yaml --dry-run=client | oc apply -f - + # TODO: commented out the next line seems not needed + # export REGISTRY_NAME="$(oc get route -n ${REGISTRY} ${REGISTRY} -o jsonpath={'.status.ingress[0].host'})" + fi + registry_login ${DESTINATION_REGISTRY} mirror_ocp 'hub' 'hub' else echo ">>>> This step to mirror ocp is not neccesary, everything looks ready: ${1}" diff --git a/deploy-disconnected-registry/olm-sync.sh b/deploy-disconnected-registry/olm-sync.sh index 7980a3471..f51bc46f9 100755 --- a/deploy-disconnected-registry/olm-sync.sh +++ b/deploy-disconnected-registry/olm-sync.sh @@ -41,10 +41,14 @@ function prepare_env() { function check_registry() { REG=${1} + if [[ ${CUSTOM_REGISTRY} == "true" ]]; then + COMMAND="" + else + COMMAND="--username ${REG_US} --password ${REG_PASS}" + fi for a in {1..30}; do - skopeo login ${REG} --authfile=${PULL_SECRET} --username ${REG_US} --password ${REG_PASS} - if [[ $? -eq 0 ]]; then + if [[ $(skopeo login ${REG} --authfile=${PULL_SECRET} ${COMMAND}) ]]; then echo "Registry: ${REG} available" break fi @@ -67,11 +71,9 @@ function mirror() { fi echo ">>>> Podman Login into Source Registry: ${SOURCE_REGISTRY}" - ${PODMAN_LOGIN_CMD} ${SOURCE_REGISTRY} -u ${REG_US} -p ${REG_PASS} --authfile=${PULL_SECRET} - ${PODMAN_LOGIN_CMD} ${SOURCE_REGISTRY} -u ${REG_US} -p ${REG_PASS} + registry_login ${SOURCE_REGISTRY} echo ">>>> Podman Login into Destination Registry: ${DESTINATION_REGISTRY}" - ${PODMAN_LOGIN_CMD} ${DESTINATION_REGISTRY} -u ${REG_US} -p ${REG_PASS} --authfile=${PULL_SECRET} - ${PODMAN_LOGIN_CMD} ${DESTINATION_REGISTRY} -u ${REG_US} -p ${REG_PASS} + registry_login ${DESTINATION_REGISTRY} if [ ! -f ~/.docker/config.json ]; then echo "INFO: missing ~/.docker/config.json config" @@ -256,11 +258,9 @@ function mirror_certified() { fi echo ">>>> Podman Login into Source Registry: ${SOURCE_REGISTRY}" - ${PODMAN_LOGIN_CMD} ${SOURCE_REGISTRY} -u ${REG_US} -p ${REG_PASS} --authfile=${PULL_SECRET} - ${PODMAN_LOGIN_CMD} ${SOURCE_REGISTRY} -u ${REG_US} -p ${REG_PASS} + registry_login ${SOURCE_REGISTRY} echo ">>>> Podman Login into Destination Registry: ${DESTINATION_REGISTRY}" - ${PODMAN_LOGIN_CMD} ${DESTINATION_REGISTRY} -u ${REG_US} -p ${REG_PASS} --authfile=${PULL_SECRET} - ${PODMAN_LOGIN_CMD} ${DESTINATION_REGISTRY} -u ${REG_US} -p ${REG_PASS} + registry_login ${DESTINATION_REGISTRY} if [ ! -f ~/.docker/config.json ]; then echo "INFO: missing ~/.docker/config.json config" diff --git a/deploy-disconnected-registry/update-global-pullsecret.sh b/deploy-disconnected-registry/update-global-pullsecret.sh index 443cbccd7..8224d574d 100755 --- a/deploy-disconnected-registry/update-global-pullsecret.sh +++ b/deploy-disconnected-registry/update-global-pullsecret.sh @@ -42,7 +42,7 @@ function prepare_env() { if [[ ${1} == 'hub' ]]; then prepare_env 'hub' - ${PODMAN_LOGIN_CMD} ${DESTINATION_REGISTRY} -u ${REG_US} -p ${REG_PASS} --authfile=${PULL_SECRET} + registry_login ${DESTINATION_REGISTRY} oc --kubeconfig=${KUBECONFIG_HUB} set data secret/pull-secret -n openshift-config --from-file=.dockerconfigjson=${PULL_SECRET} elif [[ ${1} == "edgecluster" ]]; then diff --git a/deploy-disconnected-registry/verify.sh b/deploy-disconnected-registry/verify.sh index 88804c6c5..9ac5997f9 100755 --- a/deploy-disconnected-registry/verify.sh +++ b/deploy-disconnected-registry/verify.sh @@ -19,13 +19,18 @@ elif [[ ${1} == 'edgecluster' ]]; then TG_KUBECONFIG=${EDGE_KUBECONFIG} fi -if [[ $(oc --kubeconfig=${TG_KUBECONFIG} get ns | grep ${REGISTRY} | wc -l) -eq 0 || $(oc --kubeconfig=${TG_KUBECONFIG} get -n ztpfw-registry deployment ztpfw-registry -ojsonpath='{.status.availableReplicas}') -eq 0 ]]; then - #namespace or resources does not exist. Launching the step to create it... - exit 1 -fi +if [[ ${CUSTOM_REGISTRY} == "false" ]]; then + if [[ $(oc --kubeconfig=${TG_KUBECONFIG} get ns | grep ${REGISTRY} | wc -l) -eq 0 || $(oc --kubeconfig=${TG_KUBECONFIG} get -n ztpfw-registry deployment ztpfw-registry -ojsonpath='{.status.availableReplicas}') -eq 0 ]]; then + #namespace or resources does not exist. Launching the step to create it... + exit 1 + fi -if [[ $(oc get --kubeconfig=${TG_KUBECONFIG} route -n ${REGISTRY} --no-headers | wc -l) -lt 1 ]]; then - exit 2 + if [[ $(oc get --kubeconfig=${TG_KUBECONFIG} route -n ${REGISTRY} --no-headers | wc -l) -lt 1 ]]; then + exit 2 + fi +else + # Running with Custom registry + exit 10 fi exit 0 diff --git a/deploy-disconnected-registry/verify_ocp_sync.sh b/deploy-disconnected-registry/verify_ocp_sync.sh index a166f7d2f..4784d3690 100755 --- a/deploy-disconnected-registry/verify_ocp_sync.sh +++ b/deploy-disconnected-registry/verify_ocp_sync.sh @@ -19,8 +19,7 @@ elif [[ ${1} == 'edgecluster' ]]; then TARGET_KUBECONFIG=${EDGE_KUBECONFIG} fi -echo "Logging into ${DESTINATION_REGISTRY}" -${PODMAN_LOGIN_CMD} ${DESTINATION_REGISTRY} -u ${REG_US} -p ${REG_PASS} --authfile=${PULL_SECRET} # to create a merge with the registry original adding the registry auth entry +registry_login ${DESTINATION_REGISTRY} if [[ $(oc --kubeconfig=${TARGET_KUBECONFIG} adm release info "${DESTINATION_REGISTRY}"/"${OCP_DESTINATION_REGISTRY_IMAGE_NS}":"${OC_OCP_TAG}" --registry-config="${PULL_SECRET}" | wc -l) -gt 1 ]]; then ## line 1 == error line. If found image should show more information (>1 line) #Everyting is ready exit 0 diff --git a/deploy-disconnected-registry/verify_olm_sync.sh b/deploy-disconnected-registry/verify_olm_sync.sh index 704d1097f..f5caa263c 100755 --- a/deploy-disconnected-registry/verify_olm_sync.sh +++ b/deploy-disconnected-registry/verify_olm_sync.sh @@ -41,7 +41,7 @@ elif [[ ${1} == 'edgecluster' ]]; then fi echo ">>>> Verifying OLM Sync: ${1}" -${PODMAN_LOGIN_CMD} ${DESTINATION_REGISTRY} -u ${REG_US} -p ${REG_PASS} --authfile=${PULL_SECRET} +registry_login ${DESTINATION_REGISTRY} for packagemanifest in $(oc --kubeconfig=${TGT_KUBECONFIG} get packagemanifest -n openshift-marketplace -o name ${PACKAGES_FORMATED}); do for package in $(oc --kubeconfig=${TGT_KUBECONFIG} get ${packagemanifest} -o jsonpath='{.status.channels[*].currentCSVDesc.relatedImages}' | sed "s/ /\n/g" | tr -d '[],' | sed 's/"/ /g'); do echo "Verify Package: ${package}" @@ -51,7 +51,7 @@ for packagemanifest in $(oc --kubeconfig=${TGT_KUBECONFIG} get packagemanifest - done echo ">>>> Verifying Certified OLM Sync: ${1}" -${PODMAN_LOGIN_CMD} ${DESTINATION_REGISTRY} -u ${REG_US} -p ${REG_PASS} --authfile=${PULL_SECRET} +registry_login ${DESTINATION_REGISTRY} for packagemanifest in $(oc --kubeconfig=${TGT_KUBECONFIG} get packagemanifest -n openshift-marketplace -o name ${CERTIFIED_PACKAGES_FORMATED}); do for package in $(oc --kubeconfig=${TGT_KUBECONFIG} get ${packagemanifest} -o jsonpath='{.status.channels[*].currentCSVDesc.relatedImages}' | sed "s/ /\n/g" | tr -d '[],' | sed 's/"/ /g'); do echo "Verify Package: ${package}" diff --git a/deploy-edgecluster/configure_disconnected.sh b/deploy-edgecluster/configure_disconnected.sh index ed1f3b883..8f2a6783a 100755 --- a/deploy-edgecluster/configure_disconnected.sh +++ b/deploy-edgecluster/configure_disconnected.sh @@ -170,7 +170,7 @@ function icsp_maker() { RAW_SRC=${entry%%=*} RAW_DST=${entry##*=} SRC_IMG="${RAW_SRC%%@*}" - DST_IMG="${RAW_DST%%:*}" + DST_IMG="${RAW_DST}" add_icsp_entry ${SRC_IMG} ${DST_IMG} done <${MAP_FILE} } diff --git a/deploy-hub-configs/deploy.sh b/deploy-hub-configs/deploy.sh index c5829c764..b4e638d5b 100755 --- a/deploy-hub-configs/deploy.sh +++ b/deploy-hub-configs/deploy.sh @@ -22,12 +22,21 @@ if ./verify.sh; then sed -i "s/HTTPD_SERVICE/${HTTPSERVICE}/g" 04-agent-service-config.yml pull=$(oc get secret -n openshift-config pull-secret -ojsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq -c) echo -n " .dockerconfigjson: "\'$pull\' >>05-pullsecrethub.yml - REGISTRY=ztpfw-registry - LOCAL_REG="$(oc get route -n ${REGISTRY} ${REGISTRY} -o jsonpath={'.status.ingress[0].host'})" #TODO change it to use the global common variable importing here the source commons + if [[ ${CUSTOM_REGISTRY} == "false" ]]; then + REGISTRY=ztpfw-registry + LOCAL_REG="$(oc get route -n ${REGISTRY} ${REGISTRY} -o jsonpath={'.status.ingress[0].host'})" #TODO change it to use the global common variable importing here the source commons + fi sed -i "s/CHANGEDOMAIN/${LOCAL_REG}/g" registryconf.txt - CABUNDLE=$(oc get cm -n openshift-image-registry kube-root-ca.crt --template='{{index .data "ca.crt"}}') - echo " ca-bundle.crt: |" >>01_Mirror_ConfigMap.yml - echo -n "${CABUNDLE}" | sed "s/^/ /" >>01_Mirror_ConfigMap.yml + if [[ ${CUSTOM_REGISTRY} == "true" ]]; then + export CA_CERT_DATA=$(openssl s_client -connect ${LOCAL_REG} -showcerts >01_Mirror_ConfigMap.yml + echo " ca-bundle.crt: |" >>01_Mirror_ConfigMap.yml + echo -n "${CA_CERT_DATA}" | sed "s/^/ /" >>01_Mirror_ConfigMap.yml + else + CABUNDLE=$(oc get cm -n openshift-image-registry kube-root-ca.crt --template='{{index .data "ca.crt"}}') + echo " ca-bundle.crt: |" >>01_Mirror_ConfigMap.yml + echo -n "${CABUNDLE}" | sed "s/^/ /" >>01_Mirror_ConfigMap.yml + fi echo "" >>01_Mirror_ConfigMap.yml cat registryconf.txt >>01_Mirror_ConfigMap.yml NEWTAG=${LOCAL_REG}/ocp4/openshift4:${OC_OCP_TAG} diff --git a/examples/config.yaml b/examples/config.yaml index 315700b3d..59af1f240 100644 --- a/examples/config.yaml +++ b/examples/config.yaml @@ -2,6 +2,8 @@ config: OC_OCP_VERSION: "4.10.6" OC_ACM_VERSION: "2.4" OC_ODF_VERSION: "4.9" + # optional use your own registry + REGISTRY: "myregistry.domain.local:5000" edgeclusters: - edgecluster1-name: diff --git a/images/Containerfile.pipeline b/images/Containerfile.pipeline index 7243e6a93..cd989db69 100644 --- a/images/Containerfile.pipeline +++ b/images/Containerfile.pipeline @@ -10,7 +10,7 @@ RUN curl -k -s https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/ curl -k -s https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp/latest/opm-linux.tar.gz | tar xvz -C /usr/bin && \ chmod +x /usr/bin/oc /usr/bin/opm /usr/bin/kubectl /usr/bin/jq /usr/bin/yq -RUN dnf install -y bind-utils openssh-clients httpd-tools conmon skopeo podman gettext fuse-overlayfs iputils nmap-ncat --setopt=install_weak_deps=False && \ +RUN dnf install -y bind-utils openssl openssh-clients httpd-tools conmon skopeo podman gettext fuse-overlayfs iputils nmap-ncat --setopt=install_weak_deps=False && \ dnf clean all && rm -rf /var/cache/yum COPY . /opt/ztp diff --git a/shared-utils/common.sh b/shared-utils/common.sh index 979eecb8f..bc07d50d2 100755 --- a/shared-utils/common.sh +++ b/shared-utils/common.sh @@ -3,6 +3,19 @@ # EDGECLUSTERS_FILE variable must be exported in the environment #set -x +function registry_login() { + ####### WORKAROUND: Newer versions of podman/buildah try to set overlayfs mount options when + ####### using the vfs driver, and this causes errors. + export STORAGE_DRIVER=vfs + sed -i '/^mountopt =.*/d' /etc/containers/storage.conf + + if [[ ${CUSTOM_REGISTRY} == "true" ]]; then + ${PODMAN_LOGIN_CMD} ${1} --authfile=${PULL_SECRET} + else + ${PODMAN_LOGIN_CMD} ${1} -u ${REG_US} -p ${REG_PASS} --authfile=${PULL_SECRET} + ${PODMAN_LOGIN_CMD} ${1} -u ${REG_US} -p ${REG_PASS} + fi +} function check_resource() { # 1 - Resource type: "deployment" @@ -318,3 +331,13 @@ if [[ -n ${PRESERVE_SECRET:-false} ]]; then fi export ALLEDGECLUSTERS=$(yq e '(.edgeclusters[] | keys)[]' ${EDGECLUSTERS_FILE}) + +export EDGECLUSTERS_REGISTRY=$(yq eval ".config.REGISTRY" ${EDGECLUSTERS_FILE} || null ) +if [[ ${EDGECLUSTERS_REGISTRY} == "" || ${EDGECLUSTERS_REGISTRY} == null ]]; then + export CUSTOM_REGISTRY=false + export REGISTRY=ztpfw-registry +else + export CUSTOM_REGISTRY=true + REGISTRY=$(echo ${EDGECLUSTERS_REGISTRY} | cut -d"." -f1 ) + LOCAL_REG=${EDGECLUSTERS_REGISTRY} +fi \ No newline at end of file From 748e56c2c595daec70bc655532ee026dc25c7269 Mon Sep 17 00:00:00 2001 From: eifrach Date: Mon, 20 Jun 2022 16:28:56 +0200 Subject: [PATCH 23/33] re-enabling UI testing Signed-off-by: eifrach --- .github/workflows/testing-pr.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/testing-pr.yml b/.github/workflows/testing-pr.yml index 4872b54ea..0549516e9 100644 --- a/.github/workflows/testing-pr.yml +++ b/.github/workflows/testing-pr.yml @@ -51,7 +51,6 @@ jobs: RELEASE=${RELEASE} make pipe-image-ci - name: Build and Push UI Image - continue-on-error: true # Workaround until the generation is fixed id: build-ui run: | cd ${{ github.workspace }} From de6ab20680b5fc48b0d794af1a9c3677cfbd1d63 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Tue, 21 Jun 2022 08:58:52 +0200 Subject: [PATCH 24/33] Update react-scripts to 5.0.1 --- ui/frontend/package.json | 2 +- ui/frontend/yarn.lock | 38 +++++++++++++++++++------------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ui/frontend/package.json b/ui/frontend/package.json index 7d148068f..e13c2ca2b 100644 --- a/ui/frontend/package.json +++ b/ui/frontend/package.json @@ -19,7 +19,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-router-dom": "^6.2.1", - "react-scripts": "5.0.0", + "react-scripts": "5.0.1", "typescript": "^4.4.2", "web-vitals": "^2.1.0" }, diff --git a/ui/frontend/yarn.lock b/ui/frontend/yarn.lock index b0cab22c8..edb10515e 100644 --- a/ui/frontend/yarn.lock +++ b/ui/frontend/yarn.lock @@ -4077,10 +4077,10 @@ eslint-config-prettier@^8.3.0: resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== -eslint-config-react-app@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-7.0.0.tgz#0fa96d5ec1dfb99c029b1554362ab3fa1c3757df" - integrity sha512-xyymoxtIt1EOsSaGag+/jmcywRuieQoA2JbPCjnw9HukFj9/97aGPoZVFioaotzk1K5Qt9sHO5EutZbkrAXS0g== +eslint-config-react-app@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz#73ba3929978001c5c86274c017ea57eb5fa644b4" + integrity sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA== dependencies: "@babel/core" "^7.16.0" "@babel/eslint-parser" "^7.16.3" @@ -7697,10 +7697,10 @@ react-app-polyfill@^3.0.0: regenerator-runtime "^0.13.9" whatwg-fetch "^3.6.2" -react-dev-utils@^12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526" - integrity sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ== +react-dev-utils@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" + integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ== dependencies: "@babel/code-frame" "^7.16.0" address "^1.1.2" @@ -7721,7 +7721,7 @@ react-dev-utils@^12.0.0: open "^8.4.0" pkg-up "^3.1.0" prompts "^2.4.2" - react-error-overlay "^6.0.10" + react-error-overlay "^6.0.11" recursive-readdir "^2.2.2" shell-quote "^1.7.3" strip-ansi "^6.0.1" @@ -7746,10 +7746,10 @@ react-dropzone@9.0.0: prop-types "^15.6.2" prop-types-extra "^1.1.0" -react-error-overlay@^6.0.10: - version "6.0.10" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" - integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== +react-error-overlay@^6.0.11: + version "6.0.11" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" + integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== react-is@^16.13.1, react-is@^16.3.2: version "16.13.1" @@ -7781,10 +7781,10 @@ react-router@6.2.1: dependencies: history "^5.2.0" -react-scripts@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.0.tgz#6547a6d7f8b64364ef95273767466cc577cb4b60" - integrity sha512-3i0L2CyIlROz7mxETEdfif6Sfhh9Lfpzi10CtcGs1emDQStmZfWjJbAIMtRD0opVUjQuFWqHZyRZ9PPzKCFxWg== +react-scripts@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003" + integrity sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ== dependencies: "@babel/core" "^7.16.0" "@pmmmwh/react-refresh-webpack-plugin" "^0.5.3" @@ -7802,7 +7802,7 @@ react-scripts@5.0.0: dotenv "^10.0.0" dotenv-expand "^5.1.0" eslint "^8.3.0" - eslint-config-react-app "^7.0.0" + eslint-config-react-app "^7.0.1" eslint-webpack-plugin "^3.1.1" file-loader "^6.2.0" fs-extra "^10.0.0" @@ -7819,7 +7819,7 @@ react-scripts@5.0.0: postcss-preset-env "^7.0.1" prompts "^2.4.2" react-app-polyfill "^3.0.0" - react-dev-utils "^12.0.0" + react-dev-utils "^12.0.1" react-refresh "^0.11.0" resolve "^1.20.0" resolve-url-loader "^4.0.0" From 8739a02327b27e479f7978aed78fdcf9d62e41f9 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Tue, 21 Jun 2022 09:16:09 +0200 Subject: [PATCH 25/33] Update eslint to 8.18.0 --- ui/backend/package.json | 2 +- ui/backend/yarn.lock | 95 ++++++++++++++++++++++------------------- ui/frontend/yarn.lock | 87 +++++++++++++++++++------------------ 3 files changed, 98 insertions(+), 86 deletions(-) diff --git a/ui/backend/package.json b/ui/backend/package.json index 9a84a3f91..e25038651 100644 --- a/ui/backend/package.json +++ b/ui/backend/package.json @@ -31,7 +31,7 @@ "@types/node-fetch": "2.x", "@typescript-eslint/eslint-plugin": "^5.10.1", "@typescript-eslint/parser": "^5.10.1", - "eslint": "^8.7.0", + "eslint": "^8.18.0", "prettier": "^2.5.1", "ts-node-dev": "^1.1.8", "typescript": "^4.5.5" diff --git a/ui/backend/yarn.lock b/ui/backend/yarn.lock index 71df9d223..46c8f52ea 100644 --- a/ui/backend/yarn.lock +++ b/ui/backend/yarn.lock @@ -2,19 +2,19 @@ # yarn lockfile v1 -"@eslint/eslintrc@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" - integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== +"@eslint/eslintrc@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.2.0" - globals "^13.9.0" - ignore "^4.0.6" + espree "^9.3.2" + globals "^13.15.0" + ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" - minimatch "^3.0.4" + minimatch "^3.1.2" strip-json-comments "^3.1.1" "@humanwhocodes/config-array@^0.9.2": @@ -386,15 +386,15 @@ accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -acorn-jsx@^5.3.1: +acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== +acorn@^8.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== aggregate-error@^3.1.0: version "3.1.0" @@ -825,10 +825,10 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" - integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -845,17 +845,22 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2.0: +eslint-visitor-keys@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== -eslint@^8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.7.0.tgz#22e036842ee5b7cf87b03fe237731675b4d3633c" - integrity sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w== +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@^8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.18.0.tgz#78d565d16c993d0b73968c523c0446b13da784fd" + integrity sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA== dependencies: - "@eslint/eslintrc" "^1.0.5" + "@eslint/eslintrc" "^1.3.0" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -863,17 +868,17 @@ eslint@^8.7.0: debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.0" + eslint-scope "^7.1.1" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.2.0" - espree "^9.3.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.2" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" - globals "^13.6.0" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -882,7 +887,7 @@ eslint@^8.7.0: json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" regexpp "^3.2.0" @@ -891,14 +896,14 @@ eslint@^8.7.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.2.0, espree@^9.3.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" - integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== +espree@^9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" + integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== dependencies: - acorn "^8.7.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.1.0" + acorn "^8.7.1" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" esquery@^1.4.0: version "1.4.0" @@ -1187,10 +1192,10 @@ glob@^7.0.0, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^13.6.0, globals@^13.9.0: - version "13.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" - integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== +globals@^13.15.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== dependencies: type-fest "^0.20.2" @@ -1298,11 +1303,6 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - ignore@^5.1.8, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -1616,6 +1616,13 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + minimist@^1.2.5: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" diff --git a/ui/frontend/yarn.lock b/ui/frontend/yarn.lock index edb10515e..841e9b40a 100644 --- a/ui/frontend/yarn.lock +++ b/ui/frontend/yarn.lock @@ -1065,19 +1065,19 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4" integrity sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg== -"@eslint/eslintrc@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" - integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== +"@eslint/eslintrc@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.2.0" - globals "^13.9.0" - ignore "^4.0.6" + espree "^9.3.2" + globals "^13.15.0" + ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" - minimatch "^3.0.4" + minimatch "^3.1.2" strip-json-comments "^3.1.1" "@humanwhocodes/config-array@^0.9.2": @@ -2298,7 +2298,7 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== -acorn-jsx@^5.3.1: +acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -2322,11 +2322,16 @@ acorn@^7.0.0, acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.2.4, acorn@^8.4.1, acorn@^8.7.0: +acorn@^8.2.4, acorn@^8.4.1: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== +acorn@^8.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + address@^1.0.1, address@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -4212,10 +4217,10 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" - integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -4232,11 +4237,16 @@ eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2.0: +eslint-visitor-keys@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + eslint-webpack-plugin@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-3.1.1.tgz#83dad2395e5f572d6f4d919eedaa9cf902890fcb" @@ -4249,11 +4259,11 @@ eslint-webpack-plugin@^3.1.1: schema-utils "^3.1.1" eslint@^8.3.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.7.0.tgz#22e036842ee5b7cf87b03fe237731675b4d3633c" - integrity sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w== + version "8.18.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.18.0.tgz#78d565d16c993d0b73968c523c0446b13da784fd" + integrity sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA== dependencies: - "@eslint/eslintrc" "^1.0.5" + "@eslint/eslintrc" "^1.3.0" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -4261,17 +4271,17 @@ eslint@^8.3.0: debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.0" + eslint-scope "^7.1.1" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.2.0" - espree "^9.3.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.2" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" - globals "^13.6.0" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -4280,7 +4290,7 @@ eslint@^8.3.0: json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" regexpp "^3.2.0" @@ -4289,14 +4299,14 @@ eslint@^8.3.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.2.0, espree@^9.3.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" - integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== +espree@^9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" + integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== dependencies: - acorn "^8.7.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.1.0" + acorn "^8.7.1" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" @@ -4848,10 +4858,10 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.6.0, globals@^13.9.0: - version "13.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" - integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== +globals@^13.15.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== dependencies: type-fest "^0.20.2" @@ -5162,11 +5172,6 @@ ieee754@^1.1.13, ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - ignore@^5.1.8, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -6450,7 +6455,7 @@ minimatch@3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.4: +minimatch@^3.0.4, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== From 790579a0efe38c1526f8164664254cd23dfc9df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Iranzo=20G=C3=B3mez?= Date: Tue, 21 Jun 2022 13:00:22 +0200 Subject: [PATCH 26/33] Unify eviction code into common function (#409) * fix(edgecluster): sync eviction code to find empty string with other function alredy existing * feat(evict): unify function into commnos file --- deploy-disconnected-registry/deploy.sh | 42 -------------------- deploy-edgecluster/configure_disconnected.sh | 33 --------------- shared-utils/common.sh | 42 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 75 deletions(-) diff --git a/deploy-disconnected-registry/deploy.sh b/deploy-disconnected-registry/deploy.sh index 3e9b4ae6d..72f035d73 100755 --- a/deploy-disconnected-registry/deploy.sh +++ b/deploy-disconnected-registry/deploy.sh @@ -38,48 +38,6 @@ function extract_kubeconfig() { oc --kubeconfig=${KUBECONFIG_HUB} get secret -n $edgecluster $edgecluster-admin-kubeconfig -o jsonpath='{.data.kubeconfig}' | base64 -d >${EDGE_KUBECONFIG} } -function side_evict_error() { - - KUBEC=${1} - echo ">> Looking for eviction errors" - status='SchedulingDisabled' - - conflicting_node="$(oc --kubeconfig=${KUBEC} get node --no-headers | grep ${status} | cut -f1 -d\ )" - - if [[ -z ${conflicting_node} ]]; then - echo "No masters on ${status}" - else - conflicting_daemon_pod=$(oc --kubeconfig=${KUBEC} get pod -n openshift-machine-config-operator -o wide --no-headers | grep daemon | grep ${conflicting_node} | cut -f1 -d\ ) - - # Check if conflicting_daemon_pod is not empty - if [[ -z ${conflicting_daemon_pod} ]]; then - echo "No conflicting daemon pod exists in ${conflicting_node}" - else - pattern_1="$(oc --kubeconfig=${KUBEC} logs -n openshift-machine-config-operator ${conflicting_daemon_pod} -c machine-config-daemon | grep drain.go | grep evicting | tail -1 | grep pods)" - pattern_2="$(oc --kubeconfig=${KUBEC} logs -n openshift-machine-config-operator ${conflicting_daemon_pod} -c machine-config-daemon | grep drain.go | grep "Draining failed" | tail -1 | grep pod)" - - for log_entry in "${pattern_1}" "${pattern_2}"; do - if [[ -z ${log_entry} ]]; then - echo "No Conflicting LogEntry on ${conflicting_daemon_pod}" - else - echo ">> Conflicting LogEntry Found!!" - pod=$(echo ${log_entry##*pods/} | cut -d\" -f2) - conflicting_ns=$(oc --kubeconfig=${KUBEC} get pod -A | grep ${pod} | cut -f1 -d\ ) - - echo ">> Clean Eviction triggered info: " - echo NODE: ${conflicting_node} - echo DAEMON: ${conflicting_daemon_pod} - echo NS: ${conflicting_ns} - echo LOG: ${log_entry} - echo POD: ${pod} - - oc --kubeconfig=${KUBEC} delete pod -n ${conflicting_ns} ${pod} --force --grace-period=0 - fi - done - fi - fi -} - function check_mcp() { echo Mode: ${1} diff --git a/deploy-edgecluster/configure_disconnected.sh b/deploy-edgecluster/configure_disconnected.sh index 8f2a6783a..64683edf2 100755 --- a/deploy-edgecluster/configure_disconnected.sh +++ b/deploy-edgecluster/configure_disconnected.sh @@ -175,39 +175,6 @@ function icsp_maker() { done <${MAP_FILE} } -function side_evict_error() { - - KUBEC=${1} - echo ">> Looking for eviction errors" - pattern='SchedulingDisabled' - - conflicting_node="$(oc --kubeconfig=${KUBEC} get node --no-headers | grep ${pattern} | cut -f1 -d\ )" - - if [[ -z ${conflicting_node} ]]; then - echo "No masters on ${pattern}" - else - conflicting_daemon_pod=$(oc --kubeconfig=${KUBEC} get pod -n openshift-machine-config-operator -o wide --no-headers | grep daemon | grep ${conflicting_node} | cut -f1 -d\ ) - log_entry="$(oc --kubeconfig=${KUBEC} logs -n openshift-machine-config-operator ${conflicting_daemon_pod} -c machine-config-daemon | grep drain.go | grep evicting | tail -1 | grep pods)" - - if [[ -z ${log_entry} ]]; then - echo "No Conflicting LogEntry on ${conflicting_daemon_pod}" - else - echo ">> Conflicting LogEntry Found!!" - pod=$(echo ${log_entry##*pods/} | cut -d\" -f2) - conflicting_ns=$(oc --kubeconfig=${KUBEC} get pod -A | grep ${pod} | cut -f1 -d\ ) - - echo ">> Clean Eviction triggered info: " - echo NODE: ${conflicting_node} - echo DAEMON: ${conflicting_daemon_pod} - echo NS: ${conflicting_ns} - echo LOG: ${log_entry} - echo POD: ${pod} - - oc --kubeconfig=${KUBEC} delete pod -n ${conflicting_ns} ${pod} - fi - fi -} - function wait_for_mcp_ready() { # This function waits for the MCP to be ready # It will wait for the MCP to be ready for the given number of seconds diff --git a/shared-utils/common.sh b/shared-utils/common.sh index bc07d50d2..69bb7303d 100755 --- a/shared-utils/common.sh +++ b/shared-utils/common.sh @@ -264,6 +264,48 @@ function grab_api_ingress() { export EDGE_INGRESS_IP="$(dig @${HUB_NODE_IP} +short ${REGISTRY_URL}.${EDGE_INGRESS_NAME})" } +function side_evict_error() { + + KUBEC=${1} + echo ">> Looking for eviction errors" + status='SchedulingDisabled' + + conflicting_node="$(oc --kubeconfig=${KUBEC} get node --no-headers | grep ${status} | cut -f1 -d\ )" + + if [[ -z ${conflicting_node} ]]; then + echo "No masters on ${status}" + else + conflicting_daemon_pod=$(oc --kubeconfig=${KUBEC} get pod -n openshift-machine-config-operator -o wide --no-headers | grep daemon | grep ${conflicting_node} | cut -f1 -d\ ) + + # Check if conflicting_daemon_pod is not empty + if [[ -z ${conflicting_daemon_pod} ]]; then + echo "No conflicting daemon pod exists in ${conflicting_node}" + else + pattern_1="$(oc --kubeconfig=${KUBEC} logs -n openshift-machine-config-operator ${conflicting_daemon_pod} -c machine-config-daemon | grep drain.go | grep evicting | tail -1 | grep pods)" + pattern_2="$(oc --kubeconfig=${KUBEC} logs -n openshift-machine-config-operator ${conflicting_daemon_pod} -c machine-config-daemon | grep drain.go | grep "Draining failed" | tail -1 | grep pod)" + + for log_entry in "${pattern_1}" "${pattern_2}"; do + if [[ -z ${log_entry} ]]; then + echo "No Conflicting LogEntry on ${conflicting_daemon_pod}" + else + echo ">> Conflicting LogEntry Found!!" + pod=$(echo ${log_entry##*pods/} | cut -d\" -f2) + conflicting_ns=$(oc --kubeconfig=${KUBEC} get pod -A | grep ${pod} | cut -f1 -d\ ) + + echo ">> Clean Eviction triggered info: " + echo NODE: ${conflicting_node} + echo DAEMON: ${conflicting_daemon_pod} + echo NS: ${conflicting_ns} + echo LOG: ${log_entry} + echo POD: ${pod} + + oc --kubeconfig=${KUBEC} delete pod -n ${conflicting_ns} ${pod} --force --grace-period=0 + fi + done + fi + fi +} + # EDGECLUSTERS_FILE variable must be exported in the environment export KUBECONFIG_HUB=${KUBECONFIG} From c2e6860e511017038ded09516776a4d40786f69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Iranzo=20G=C3=B3mez?= Date: Tue, 21 Jun 2022 13:08:38 +0200 Subject: [PATCH 27/33] style: Use precommit for formatting --- deploy-disconnected-registry/common.sh | 2 +- deploy-disconnected-registry/ocp-sync.sh | 4 ++-- deploy-disconnected-registry/olm-sync.sh | 2 +- deploy-disconnected-registry/verify.sh | 2 +- deploy-hub-configs/deploy.sh | 14 +++++++------- examples/config.yaml | 2 +- shared-utils/common.sh | 6 +++--- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/deploy-disconnected-registry/common.sh b/deploy-disconnected-registry/common.sh index 398c33c48..e5966e781 100755 --- a/deploy-disconnected-registry/common.sh +++ b/deploy-disconnected-registry/common.sh @@ -88,7 +88,7 @@ function trust_internal_registry() { export CA_CERT_DATA=$(oc --kubeconfig=${KBKNFG} get secret -n openshift-ingress router-certs-default -o go-template='{{index .data "tls.crt"}}') echo ">> Cert: ${PATH_CA_CERT}" else - export CA_CERT_DATA=$(openssl s_client -connect ${LOCAL_REG} -showcerts >>> Podman Login into Source Registry: ${SOURCE_REGISTRY}" registry_login ${SOURCE_REGISTRY} echo ">>>> Podman Login into Destination Registry: ${DESTINATION_REGISTRY}" - registry_login ${DESTINATION_REGISTRY} + registry_login ${DESTINATION_REGISTRY} if [ ! -f ~/.docker/config.json ]; then echo "INFO: missing ~/.docker/config.json config" diff --git a/deploy-disconnected-registry/verify.sh b/deploy-disconnected-registry/verify.sh index 9ac5997f9..95c74cc5e 100755 --- a/deploy-disconnected-registry/verify.sh +++ b/deploy-disconnected-registry/verify.sh @@ -29,7 +29,7 @@ if [[ ${CUSTOM_REGISTRY} == "false" ]]; then exit 2 fi else - # Running with Custom registry + # Running with Custom registry exit 10 fi diff --git a/deploy-hub-configs/deploy.sh b/deploy-hub-configs/deploy.sh index b4e638d5b..a417a0bbc 100755 --- a/deploy-hub-configs/deploy.sh +++ b/deploy-hub-configs/deploy.sh @@ -23,19 +23,19 @@ if ./verify.sh; then pull=$(oc get secret -n openshift-config pull-secret -ojsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq -c) echo -n " .dockerconfigjson: "\'$pull\' >>05-pullsecrethub.yml if [[ ${CUSTOM_REGISTRY} == "false" ]]; then - REGISTRY=ztpfw-registry - LOCAL_REG="$(oc get route -n ${REGISTRY} ${REGISTRY} -o jsonpath={'.status.ingress[0].host'})" #TODO change it to use the global common variable importing here the source commons + REGISTRY=ztpfw-registry + LOCAL_REG="$(oc get route -n ${REGISTRY} ${REGISTRY} -o jsonpath={'.status.ingress[0].host'})" #TODO change it to use the global common variable importing here the source commons fi sed -i "s/CHANGEDOMAIN/${LOCAL_REG}/g" registryconf.txt if [[ ${CUSTOM_REGISTRY} == "true" ]]; then - export CA_CERT_DATA=$(openssl s_client -connect ${LOCAL_REG} -showcerts >01_Mirror_ConfigMap.yml + export CA_CERT_DATA=$(openssl s_client -connect ${LOCAL_REG} -showcerts >01_Mirror_ConfigMap.yml echo " ca-bundle.crt: |" >>01_Mirror_ConfigMap.yml echo -n "${CA_CERT_DATA}" | sed "s/^/ /" >>01_Mirror_ConfigMap.yml else - CABUNDLE=$(oc get cm -n openshift-image-registry kube-root-ca.crt --template='{{index .data "ca.crt"}}') - echo " ca-bundle.crt: |" >>01_Mirror_ConfigMap.yml - echo -n "${CABUNDLE}" | sed "s/^/ /" >>01_Mirror_ConfigMap.yml + CABUNDLE=$(oc get cm -n openshift-image-registry kube-root-ca.crt --template='{{index .data "ca.crt"}}') + echo " ca-bundle.crt: |" >>01_Mirror_ConfigMap.yml + echo -n "${CABUNDLE}" | sed "s/^/ /" >>01_Mirror_ConfigMap.yml fi echo "" >>01_Mirror_ConfigMap.yml cat registryconf.txt >>01_Mirror_ConfigMap.yml diff --git a/examples/config.yaml b/examples/config.yaml index 59af1f240..21f43323b 100644 --- a/examples/config.yaml +++ b/examples/config.yaml @@ -2,7 +2,7 @@ config: OC_OCP_VERSION: "4.10.6" OC_ACM_VERSION: "2.4" OC_ODF_VERSION: "4.9" - # optional use your own registry + # optional use your own registry REGISTRY: "myregistry.domain.local:5000" edgeclusters: diff --git a/shared-utils/common.sh b/shared-utils/common.sh index 69bb7303d..acad62677 100755 --- a/shared-utils/common.sh +++ b/shared-utils/common.sh @@ -374,12 +374,12 @@ fi export ALLEDGECLUSTERS=$(yq e '(.edgeclusters[] | keys)[]' ${EDGECLUSTERS_FILE}) -export EDGECLUSTERS_REGISTRY=$(yq eval ".config.REGISTRY" ${EDGECLUSTERS_FILE} || null ) +export EDGECLUSTERS_REGISTRY=$(yq eval ".config.REGISTRY" ${EDGECLUSTERS_FILE} || null) if [[ ${EDGECLUSTERS_REGISTRY} == "" || ${EDGECLUSTERS_REGISTRY} == null ]]; then export CUSTOM_REGISTRY=false export REGISTRY=ztpfw-registry else export CUSTOM_REGISTRY=true - REGISTRY=$(echo ${EDGECLUSTERS_REGISTRY} | cut -d"." -f1 ) + REGISTRY=$(echo ${EDGECLUSTERS_REGISTRY} | cut -d"." -f1) LOCAL_REG=${EDGECLUSTERS_REGISTRY} -fi \ No newline at end of file +fi From e55202ccb177972712446f8c4641a38d67f86b37 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 22 Jun 2022 09:21:53 +0200 Subject: [PATCH 28/33] MGMT-10846: Fix setting "clean" snapshot for isDirty comparision --- .../src/components/K8SStateContext.tsx | 98 ++++++++----------- 1 file changed, 39 insertions(+), 59 deletions(-) diff --git a/ui/frontend/src/components/K8SStateContext.tsx b/ui/frontend/src/components/K8SStateContext.tsx index 017a8b2f0..cfd327ac3 100644 --- a/ui/frontend/src/components/K8SStateContext.tsx +++ b/ui/frontend/src/components/K8SStateContext.tsx @@ -123,72 +123,52 @@ export const K8SStateContextProvider: React.FC<{ usernameValidation, ]); - const fieldValues: K8SStateContextDataFields = React.useMemo( - () => ({ - username, - password, - apiaddr, - ingressIp, - domain, - originalDomain, - customCerts, - }), - [username, password, apiaddr, ingressIp, domain, originalDomain, customCerts], - ); + const _fv: K8SStateContextDataFields = { + username, + password, + apiaddr, + ingressIp, + domain, + originalDomain, + customCerts, + }; + + const fieldValues = React.useRef(_fv); + fieldValues.current = _fv; const [snapshot, setSnapshot] = React.useState(); const setClean = React.useCallback(() => { - setSnapshot(fieldValues); + setSnapshot(fieldValues.current); }, [fieldValues]); const isDirty = React.useCallback((): boolean => { - return !isEqual(fieldValues, snapshot); + return !isEqual(fieldValues.current, snapshot); }, [fieldValues, snapshot]); - const value = React.useMemo( - () => ({ - ...fieldValues, - - isDirty, - setClean, - isAllValid, - - usernameValidation, - handleSetUsername, - - passwordValidation, - handleSetPassword, - - apiaddrValidation, - handleSetApiaddr, - - ingressIpValidation, - handleSetIngressIp, - - domainValidation, - handleSetDomain, - - customCertsValidation, - setCustomCertificate, - }), - [ - fieldValues, - isDirty, - setClean, - isAllValid, - usernameValidation, - handleSetUsername, - passwordValidation, - handleSetPassword, - apiaddrValidation, - handleSetApiaddr, - ingressIpValidation, - handleSetIngressIp, - domainValidation, - handleSetDomain, - customCertsValidation, - setCustomCertificate, - ], - ); + const value = { + ...fieldValues.current, + + isDirty, + setClean, + isAllValid, + + usernameValidation, + handleSetUsername, + + passwordValidation, + handleSetPassword, + + apiaddrValidation, + handleSetApiaddr, + + ingressIpValidation, + handleSetIngressIp, + + domainValidation, + handleSetDomain, + + customCertsValidation, + setCustomCertificate, + }; return {children}; }; From 0b42124c2fef4af7cd5f33636633640fda87ed53 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Tue, 21 Jun 2022 15:33:03 +0200 Subject: [PATCH 29/33] [UI] Be more talkative for the user waiting for data to be persisted --- .../PersistPage/PersistPageBottom.tsx | 23 ++++--------------- .../__snapshots__/PersistPage.test.tsx.snap | 7 +++++- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx b/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx index 00fe3ec1d..f2baf610a 100644 --- a/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx +++ b/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx @@ -63,7 +63,7 @@ export const PersistPageBottom: React.FC = () => {
- ) : error === undefined ? ( + ) : ( <> { /> - Saving settings for your edge cluster... - - - - - - ) : ( - <> - - - - - Settings succesfully saved, it might take several minutes for cluster to reconcile. + Saving settings for your edge cluster, it might take several minutes for cluster to + reconcile. Please keep this window open until the process is finished. - + - Saving settings for your edge cluster... + Saving settings for your edge cluster, it might take several minutes for cluster to reconcile. +
+
+ Please keep this window open until the process is finished.
Date: Wed, 22 Jun 2022 09:44:15 +0200 Subject: [PATCH 30/33] MGMT-10892 Safe navigation to the /wizard/persist page for no change If the user navigates to the /persist page either directly without providing data first or just by hitting F5 during persistence, the actual persist() routine is skipped and the user is navigated to the /welcome screen instead. This fix avoids blind saving of default data. --- ui/frontend/src/components/K8SStateContext.tsx | 2 +- .../src/components/PersistPage/PersistPageBottom.tsx | 6 +++++- ui/frontend/src/components/PersistPage/persist.ts | 9 ++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ui/frontend/src/components/K8SStateContext.tsx b/ui/frontend/src/components/K8SStateContext.tsx index cfd327ac3..7199f13e1 100644 --- a/ui/frontend/src/components/K8SStateContext.tsx +++ b/ui/frontend/src/components/K8SStateContext.tsx @@ -136,7 +136,7 @@ export const K8SStateContextProvider: React.FC<{ const fieldValues = React.useRef(_fv); fieldValues.current = _fv; - const [snapshot, setSnapshot] = React.useState(); + const [snapshot, setSnapshot] = React.useState(_fv); const setClean = React.useCallback(() => { setSnapshot(fieldValues.current); }, [fieldValues]); diff --git a/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx b/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx index 00fe3ec1d..d81396737 100644 --- a/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx +++ b/ui/frontend/src/components/PersistPage/PersistPageBottom.tsx @@ -39,7 +39,11 @@ export const PersistPageBottom: React.FC = () => { navigateToNewDomain(state.domain, '/wizard/final'), ); } else { - console.warn('No change to persist on the PersistPage'); + console.warn( + 'No change to persist on the PersistPage. The reconcilliation might be still running if the user got here after page refresh.', + ); + // TODO: Find out what's going on and resume the progress bar for the user + navigateToNewDomain(state.domain, '/wizard/welcome'); } }, [retry, setError, progress.setProgress, state]); diff --git a/ui/frontend/src/components/PersistPage/persist.ts b/ui/frontend/src/components/PersistPage/persist.ts index 7988a8121..257bcfb6d 100644 --- a/ui/frontend/src/components/PersistPage/persist.ts +++ b/ui/frontend/src/components/PersistPage/persist.ts @@ -163,7 +163,14 @@ export const navigateToNewDomain = async (domain: string, contextPath: string) = // We can not check livenessProbe on the new domain due to CORS // We can not use pod serving old domain either since it will be terminated and the route changed // So just wait... - const ztpfwUrl = `https://${ZTPFW_UI_ROUTE_PREFIX}.apps.${domain}${contextPath}`; + let ztpfwUrl: string; + if (!domain) { + // fallback + ztpfwUrl = `${window.location.origin}${contextPath}`; + } else { + ztpfwUrl = `https://${ZTPFW_UI_ROUTE_PREFIX}.apps.${domain}${contextPath}`; + } + console.info('Changes are persisted, about to navigate to the new domain: ', ztpfwUrl); // We should go with following: window.location.replace(ztpfwUrl); From ec62c8ce0f8ea36c20dfe4ec662935956e640d6c Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 22 Jun 2022 12:24:27 +0200 Subject: [PATCH 31/33] Add jest-location-mock dev dependency --- ui/frontend/package.json | 1 + ui/frontend/yarn.lock | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/ui/frontend/package.json b/ui/frontend/package.json index e13c2ca2b..a09acf8fc 100644 --- a/ui/frontend/package.json +++ b/ui/frontend/package.json @@ -30,6 +30,7 @@ "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest-fetch-mock": "^3.0.3", + "jest-location-mock": "^1.0.9", "prettier": "^2.5.1", "string.prototype.replaceall": "^1.0.6" }, diff --git a/ui/frontend/yarn.lock b/ui/frontend/yarn.lock index 841e9b40a..32b4de0d1 100644 --- a/ui/frontend/yarn.lock +++ b/ui/frontend/yarn.lock @@ -1110,6 +1110,11 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@jedmao/location@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@jedmao/location/-/location-3.0.0.tgz#f2b24e937386f95252f3a1fefbf7ca2e0a4b87e9" + integrity sha512-p7mzNlgJbCioUYLUEKds3cQG4CHONVFJNYqMe6ocEtENCL/jYmMo1Q3ApwsMmU+L0ZkaDJEyv4HokaByLoPwlQ== + "@jest/console@^27.4.6": version "27.4.6" resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.4.6.tgz#0742e6787f682b22bdad56f9db2a8a77f6a86107" @@ -3761,6 +3766,11 @@ diff-sequences@^27.4.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.4.0.tgz#d783920ad8d06ec718a060d00196dfef25b132a5" integrity sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww== +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -5621,6 +5631,16 @@ jest-diff@^27.0.0, jest-diff@^27.4.6: jest-get-type "^27.4.0" pretty-format "^27.4.6" +jest-diff@^27.0.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + jest-docblock@^27.4.0: version "27.4.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.4.0.tgz#06c78035ca93cbbb84faf8fce64deae79a59f69f" @@ -5677,6 +5697,11 @@ jest-get-type@^27.4.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.4.0.tgz#7503d2663fffa431638337b3998d39c5e928e9b5" integrity sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ== +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + jest-haste-map@^27.4.6: version "27.4.6" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.4.6.tgz#c60b5233a34ca0520f325b7e2cc0a0140ad0862a" @@ -5728,6 +5753,14 @@ jest-leak-detector@^27.4.6: jest-get-type "^27.4.0" pretty-format "^27.4.6" +jest-location-mock@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/jest-location-mock/-/jest-location-mock-1.0.9.tgz#f4466362423b273e12ca3716467a3d478ce78fa8" + integrity sha512-DN/v7Zsa3N4uGgWTCrMrPPxhZORr/4N5gi+u7Tk6sLdORYplrC0//wfFN5FOtx4ZdQzDVfY6rLa4d+wfTKzQHw== + dependencies: + "@jedmao/location" "^3.0.0" + jest-diff "^27.0.1" + jest-matcher-utils@^27.4.6: version "27.4.6" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz#53ca7f7b58170638590e946f5363b988775509b8" @@ -7568,6 +7601,15 @@ pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.4.6: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" From 0f64e4f1c86452807942ba232b658e7a2a1e5b30 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 22 Jun 2022 12:24:48 +0200 Subject: [PATCH 32/33] Fix unit tests --- .../PersistPage/__snapshots__/PersistPage.test.tsx.snap | 6 +++--- ui/frontend/src/setupTests.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap b/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap index 18d378e9f..dfeda2e14 100644 --- a/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap +++ b/ui/frontend/src/components/PersistPage/__snapshots__/PersistPage.test.tsx.snap @@ -160,20 +160,20 @@ exports[`PersistPage can render 1`] = ` - Saving Ingress IP + 0%
Date: Thu, 23 Jun 2022 12:27:21 +0100 Subject: [PATCH 33/33] updating Clusters_0422_network_image to show optional int network (#443) --- ...Shift_Installing_Clusters_0422_network.png | Bin 126371 -> 128020 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/documentation/ztp-for-factories/images/225_OpenShift_Installing_Clusters_0422_network.png b/documentation/ztp-for-factories/images/225_OpenShift_Installing_Clusters_0422_network.png index 83b2ab8ffed40d1a2170052a0e2b4fac3a554efc..ce45eb99602ad715ba40013989ef1bc69cfb7b3a 100644 GIT binary patch literal 128020 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�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 literal 126371 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 II;Vst01F#b7ytkO