From 045d510dc80c4d472f9a570d37a27d660adbd23e Mon Sep 17 00:00:00 2001 From: Gerry-Forde <63045020+Gerry-Forde@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:24:33 +0100 Subject: [PATCH 01/22] RHIDP-8635 - Comprehensive documentation for developers on adding localization support to custom plugins --- .../assembly-customizing-the-appearance.adoc | 1 - assemblies/assembly-localization-in-rhdh.adoc | 20 +++++++++++++ .../proc-customize-rhdh-language.adoc | 23 +++++++++++++++ .../proc-enabling-localization-in-rhdh.adoc | 29 +++++++++++++++++++ titles/customizing/master.adoc | 2 ++ 5 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 assemblies/assembly-localization-in-rhdh.adoc create mode 100644 modules/customizing-the-appearance/proc-customize-rhdh-language.adoc create mode 100644 modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc diff --git a/assemblies/assembly-customizing-the-appearance.adoc b/assemblies/assembly-customizing-the-appearance.adoc index 00029fff46..8ef97012c7 100644 --- a/assemblies/assembly-customizing-the-appearance.adoc +++ b/assemblies/assembly-customizing-the-appearance.adoc @@ -22,7 +22,6 @@ You can also customize some components from the {product-short} GUI, such as the include::modules/customizing-the-appearance/proc-customize-rhdh-theme-mode.adoc[leveloffset=+1] - include::modules/customizing-the-appearance/proc-customize-rhdh-branding-logo.adoc[leveloffset=+1] diff --git a/assemblies/assembly-localization-in-rhdh.adoc b/assemblies/assembly-localization-in-rhdh.adoc new file mode 100644 index 0000000000..3f93f608fe --- /dev/null +++ b/assemblies/assembly-localization-in-rhdh.adoc @@ -0,0 +1,20 @@ +:_mod-docs-content-type: ASSEMBLY + +[id="assembly-localization-in-rhdh_{context}"] += Localization in {product} + +The following default theme configurations are available for {product}: + +You can change or disable particular parameters in a default theme or create a fully customized theme by modifying the `app-config-rhdh.yaml` file. From the `app-config-rhdh.yaml` file, you can customize common theme components, including the following: + +* Company name and logo +* Font color, size, and style of text in paragraphs, headings, headers, and buttons +* Header color, gradient, and shape +* Button color +* Navigation indicator color + +You can also customize some components from the {product-short} GUI, such as the theme mode (*Light Theme*, *Dark Theme*, or *Auto*). + +include::modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc[leveloffset=+1] + +include::modules/customizing-the-appearance/proc-customize-rhdh-language.adoc[leveloffset=+1] \ No newline at end of file diff --git a/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc b/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc new file mode 100644 index 0000000000..1376290b81 --- /dev/null +++ b/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc @@ -0,0 +1,23 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-customizing-rhdh-language_{context}"] += Customizing the language for your {product-short} instance + +The language settings of {product-very-short} use English by default. You can choose to use one of the following languages instead. + +.Supported languages + +* French + +The default language is English, which automatically sets the light or dark theme based on your system preferences. + +.Prerequisites + +* You are logged in to the {product-short} web console. + +.Procedure + +. From the {product-short} web console, click *Settings*. +. From the *Appearance* panel, click the language dropdown to select your language of choice. ++ +image::rhdh/customize-language-dropdown.png[] \ No newline at end of file diff --git a/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc b/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc new file mode 100644 index 0000000000..ed31e79b60 --- /dev/null +++ b/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc @@ -0,0 +1,29 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-enabling-localization-in-rhdh_{context}"] += Enabling localization in {product-short} + +The language settings of {product-very-short} use English by default. You can choose to use one of the following languages instead. + +.Supported languages + +* French (fr) + +The default language is English, which automatically sets the light or dark theme based on your system preferences. + +.Prerequisites + +.Procedure +. To enable localization in your {product-very-short} application, add the `i18n` section to your custom {product-short} `{my-app-config-file}` configuration file: ++ +[id=i18n] +.`{my-app-config-file}` fragment with localization `i18n` fields +[source,yaml,subs="+quotes"] +---- +i18n: + locales: # List of supported locales. Must include `en`, otherwise the translation framework will fail to load. + - en + - de + - it + defaultLocale: en # Optional. Defaults to `en` if not specified. +---- \ No newline at end of file diff --git a/titles/customizing/master.adoc b/titles/customizing/master.adoc index d5c480b50e..29898dda63 100644 --- a/titles/customizing/master.adoc +++ b/titles/customizing/master.adoc @@ -45,3 +45,5 @@ include::assemblies/assembly-customizing-the-homepage.adoc[leveloffset=+1] include::assemblies/assembly-customizing-the-quick-access-card.adoc[leveloffset=+1] include::modules/customizing/proc-customizing-rhdh-metadata-card.adoc[leveloffset=+1] + +include::assemblies/assembly-localization-in-rhdh.adoc[leveloffset=+1] From aedbf889d130a9a57509f4d2ffe99a22a5f34e8d Mon Sep 17 00:00:00 2001 From: Gerry-Forde <63045020+Gerry-Forde@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:26:07 +0100 Subject: [PATCH 02/22] RHIDP-8635 - Comprehensive documentation for developers on adding localization support to custom plugins --- images/rhdh/customize-language-dropdown.png | Bin 0 -> 71735 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/rhdh/customize-language-dropdown.png diff --git a/images/rhdh/customize-language-dropdown.png b/images/rhdh/customize-language-dropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..972b0a63396ef3a56130c78d8408673ad858b8e1 GIT binary patch literal 71735 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 S`CuLc5O})!xvX Date: Thu, 2 Oct 2025 11:58:28 +0100 Subject: [PATCH 03/22] RHIDP-8635 - Comprehensive documentation for developers on adding localization support to custom plugins --- .../proc-enabling-localization-in-rhdh.adoc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc b/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc index ed31e79b60..d3af6d2d15 100644 --- a/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc +++ b/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc @@ -1,12 +1,12 @@ :_mod-docs-content-type: PROCEDURE [id="proc-enabling-localization-in-rhdh_{context}"] -= Enabling localization in {product-short} += Enabling the localization framework in {product-short} The language settings of {product-very-short} use English by default. You can choose to use one of the following languages instead. .Supported languages - +* English (en) * French (fr) The default language is English, which automatically sets the light or dark theme based on your system preferences. @@ -14,7 +14,7 @@ The default language is English, which automatically sets the light or dark them .Prerequisites .Procedure -. To enable localization in your {product-very-short} application, add the `i18n` section to your custom {product-short} `{my-app-config-file}` configuration file: +. To enable the localization framework in your {product-very-short} application, add the `i18n` section to your custom {product-short} `{my-app-config-file}` configuration file: + [id=i18n] .`{my-app-config-file}` fragment with localization `i18n` fields @@ -23,7 +23,6 @@ The default language is English, which automatically sets the light or dark them i18n: locales: # List of supported locales. Must include `en`, otherwise the translation framework will fail to load. - en - - de - - it + - fr defaultLocale: en # Optional. Defaults to `en` if not specified. ---- \ No newline at end of file From c12909a2d5b8f9c4703291804e8160e329617082 Mon Sep 17 00:00:00 2001 From: Gerry-Forde <63045020+Gerry-Forde@users.noreply.github.com> Date: Thu, 2 Oct 2025 14:07:20 +0100 Subject: [PATCH 04/22] RHIDP-8635 - Comprehensive documentation for developers on adding localization support to custom plugins --- assemblies/assembly-localization-in-rhdh.adoc | 12 ------------ .../proc-customize-rhdh-language.adoc | 2 +- .../proc-enabling-localization-in-rhdh.adoc | 1 + 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/assemblies/assembly-localization-in-rhdh.adoc b/assemblies/assembly-localization-in-rhdh.adoc index 3f93f608fe..324e2d37cf 100644 --- a/assemblies/assembly-localization-in-rhdh.adoc +++ b/assemblies/assembly-localization-in-rhdh.adoc @@ -3,18 +3,6 @@ [id="assembly-localization-in-rhdh_{context}"] = Localization in {product} -The following default theme configurations are available for {product}: - -You can change or disable particular parameters in a default theme or create a fully customized theme by modifying the `app-config-rhdh.yaml` file. From the `app-config-rhdh.yaml` file, you can customize common theme components, including the following: - -* Company name and logo -* Font color, size, and style of text in paragraphs, headings, headers, and buttons -* Header color, gradient, and shape -* Button color -* Navigation indicator color - -You can also customize some components from the {product-short} GUI, such as the theme mode (*Light Theme*, *Dark Theme*, or *Auto*). - include::modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc[leveloffset=+1] include::modules/customizing-the-appearance/proc-customize-rhdh-language.adoc[leveloffset=+1] \ No newline at end of file diff --git a/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc b/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc index 1376290b81..866547529e 100644 --- a/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc +++ b/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc @@ -6,7 +6,7 @@ The language settings of {product-very-short} use English by default. You can choose to use one of the following languages instead. .Supported languages - +* English * French The default language is English, which automatically sets the light or dark theme based on your system preferences. diff --git a/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc b/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc index d3af6d2d15..0c4acf8a26 100644 --- a/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc +++ b/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc @@ -2,6 +2,7 @@ [id="proc-enabling-localization-in-rhdh_{context}"] = Enabling the localization framework in {product-short} +Enabling localization enhances accessibility, improves the user experience for a global audience, and assists organizations in meeting language requirements in specific regions. The language settings of {product-very-short} use English by default. You can choose to use one of the following languages instead. From 0724188c28ca0b75cb79c5390f3840a538ca612a Mon Sep 17 00:00:00 2001 From: Gerry-Forde <63045020+Gerry-Forde@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:16:38 +0100 Subject: [PATCH 05/22] RHIDP-8635 - Comprehensive documentation for developers on adding localization support to custom plugins --- images/rhdh/customize-language-dropdown.png | Bin 71735 -> 51705 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/rhdh/customize-language-dropdown.png b/images/rhdh/customize-language-dropdown.png index 972b0a63396ef3a56130c78d8408673ad858b8e1..ab668d60f32c6da6801c2b1748a1bff0e689e11d 100644 GIT binary patch literal 51705 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 I3;x&t0An+OAOHXW literal 71735 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 S`CuLc5O})!xvX Date: Fri, 10 Oct 2025 10:16:18 +0100 Subject: [PATCH 06/22] RHIDP-8635 - Comprehensive documentation for developers on adding localization support to custom plugins --- .../proc-customize-rhdh-language.adoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc b/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc index 866547529e..b75b598bb7 100644 --- a/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc +++ b/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc @@ -11,6 +11,11 @@ The language settings of {product-very-short} use English by default. You can ch The default language is English, which automatically sets the light or dark theme based on your system preferences. +[NOTE] +==== +English and French are the supported languages in {product-very-short} 1.8. You can add other languages in the the `i18n` section of your `{my-app-config-file}` configuration file. +==== + .Prerequisites * You are logged in to the {product-short} web console. From b17a4128ac42e0b38d1a7240eb713993c7a348b3 Mon Sep 17 00:00:00 2001 From: Priyanka Abel Date: Mon, 29 Sep 2025 22:54:40 +0530 Subject: [PATCH 07/22] RHDHBUGS-2051: Adding further changes (#1417) * Adding further changes * Minor changes * Incorporated Karthik's comments * Minor comment * Incorporated Jana's comments * Minor comment --- .../proc-changing-your-llm-provider.adoc | 5 +++++ .../proc-installing-and-configuring-lightspeed.adoc | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/developer-lightspeed/proc-changing-your-llm-provider.adoc b/modules/developer-lightspeed/proc-changing-your-llm-provider.adoc index 87d7676b0f..dee81aabbe 100644 --- a/modules/developer-lightspeed/proc-changing-your-llm-provider.adoc +++ b/modules/developer-lightspeed/proc-changing-your-llm-provider.adoc @@ -28,6 +28,11 @@ lightspeed: url: __ token: __ ---- ++ +[NOTE] +==== +In Developer preview, only one LLM server is supported at a time. +==== ** Optional: You can set the `id`, `url`, and `token` values in a Kubernetes Secret and reference them as environment variables using the `envFrom` section. [source,yaml] ---- diff --git a/modules/developer-lightspeed/proc-installing-and-configuring-lightspeed.adoc b/modules/developer-lightspeed/proc-installing-and-configuring-lightspeed.adoc index b0d92a509d..449e8d9c7c 100644 --- a/modules/developer-lightspeed/proc-installing-and-configuring-lightspeed.adoc +++ b/modules/developer-lightspeed/proc-installing-and-configuring-lightspeed.adoc @@ -71,7 +71,7 @@ data: + [IMPORTANT] ==== -Do not remove the block in the `llm_providers` section. This requirement is crucial when working with {ls-short} because of limitations discovered in Road Core. If you decide to use an alternative LLM provider, you should refer to additional information in the guide. For more information, see link:{developer-lightspeed-link}#proc-changing-your-llm-provider[Changing your LLM provider]. +Do not remove the block in the `llm_providers` section. This requirement is crucial when working with {ls-short} because of limitations discovered in Road Core. If you decide to use an alternative LLM provider, you should refer to additional information in the guide. For more information, see {developer-lightspeed-link}#proc-changing-your-llm-provider_customizing-developer-lightspeed[Changing your LLM provider]. ==== .. Optional: Configure the number of workers that scale the REST API by specifying the following example to the `ols_config.max_workers` parameter in the {rcs-short} ConfigMap. + From 64cfeadc040b6d281b6bb28bb1179b4a31d160bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20Flore-Th=C3=A9bault?= Date: Tue, 30 Sep 2025 10:37:14 +0200 Subject: [PATCH 08/22] RHIDP-7975 Enabling authentication procedures with mandatory steps only (#1266) Co-authored-by: Jessica He --- .../config/vocabularies/RHDH/accept.txt | 5 + .vale.ini | 1 + ...ly-authenticating-with-the-guest-user.adoc | 3 +- ...hentication-with-mandatory-steps-only.adoc | 20 ++++ .../assembly-enabling-authentication.adoc | 4 +- ...-authentication-and-user-provisioning.adoc | 21 ++-- ...est-user-on-a-helm-based-installation.adoc | 7 +- ...ser-on-an-operator-based-installation.adoc | 7 +- ...ers-from-rhbk-to-the-software-catalog.adoc | 15 +-- ...bling-user-authentication-with-github.adoc | 80 ++++++------- ...r-authentication-with-microsoft-azure.adoc | 88 +++++++-------- ...nabling-user-authentication-with-rhbk.adoc | 106 +++++++++--------- .../master.adoc | 2 + 13 files changed, 189 insertions(+), 170 deletions(-) create mode 100644 assemblies/assembly-enabling-authentication-with-mandatory-steps-only.adoc diff --git a/.vale-styles/config/vocabularies/RHDH/accept.txt b/.vale-styles/config/vocabularies/RHDH/accept.txt index 907ac9240c..b6c171fff1 100644 --- a/.vale-styles/config/vocabularies/RHDH/accept.txt +++ b/.vale-styles/config/vocabularies/RHDH/accept.txt @@ -1 +1,6 @@ +cron +Entra +IdP +Operator +MSGraph scaffolder diff --git a/.vale.ini b/.vale.ini index 3993bb9a9a..424c5c03e7 100644 --- a/.vale.ini +++ b/.vale.ini @@ -23,3 +23,4 @@ BasedOnStyles = RedHat,DeveloperHub,AsciiDocDITA AsciiDocDITA.AttributeReference = NO AsciiDocDITA.ShortDescription = NO AsciiDocDITA.CrossReference = NO +AsciiDocDITA.ConditionalCode = NO diff --git a/assemblies/assembly-authenticating-with-the-guest-user.adoc b/assemblies/assembly-authenticating-with-the-guest-user.adoc index f6da71658a..4ee7c0ee98 100644 --- a/assemblies/assembly-authenticating-with-the-guest-user.adoc +++ b/assemblies/assembly-authenticating-with-the-guest-user.adoc @@ -3,8 +3,7 @@ [id="authenticating-with-the-guest-user_{context}"] = Authenticating with the Guest user -To explore {product-short} features, you can skip configuring authentication and authorization. -You can configure {product-short} to log in as a Guest user and access {product-short} features. +For trial or non-production environments, you can enable guest access to skip configuring authentication and authorization and explore {product-short} features. include::modules/authentication/proc-authenticationg-with-the-guest-user-on-an-operator-based-installation.adoc[leveloffset=+1] diff --git a/assemblies/assembly-enabling-authentication-with-mandatory-steps-only.adoc b/assemblies/assembly-enabling-authentication-with-mandatory-steps-only.adoc new file mode 100644 index 0000000000..c6399552c9 --- /dev/null +++ b/assemblies/assembly-enabling-authentication-with-mandatory-steps-only.adoc @@ -0,0 +1,20 @@ +:_mod-docs-content-type: ASSEMBLY +:optional-steps: disable + +[id='enabling-authentication-with-mandatory-steps-only'] += Enabling authentication in {product} (with mandatory steps only) + +include::modules/authentication/con-understanding-authentication-and-user-provisioning.adoc[leveloffset=+1] + + +include::assembly-authenticating-with-the-guest-user.adoc[leveloffset=+1] + + +include::modules/authentication/proc-enabling-user-authentication-with-rhbk.adoc[leveloffset=+1] + + +include::modules/authentication/proc-enabling-user-authentication-with-github.adoc[leveloffset=+1] + + +include::modules/authentication/proc-enabling-user-authentication-with-microsoft-azure.adoc[leveloffset=+1] + diff --git a/assemblies/assembly-enabling-authentication.adoc b/assemblies/assembly-enabling-authentication.adoc index ba40346624..64be70baa2 100644 --- a/assemblies/assembly-enabling-authentication.adoc +++ b/assemblies/assembly-enabling-authentication.adoc @@ -1,11 +1,9 @@ :_mod-docs-content-type: ASSEMBLY +:optional-steps: enable [id='enabling-authentication'] = Enabling authentication in {product} - - - include::modules/authentication/con-understanding-authentication-and-user-provisioning.adoc[leveloffset=+1] diff --git a/modules/authentication/con-understanding-authentication-and-user-provisioning.adoc b/modules/authentication/con-understanding-authentication-and-user-provisioning.adoc index b2f864608e..9c7ff4413a 100644 --- a/modules/authentication/con-understanding-authentication-and-user-provisioning.adoc +++ b/modules/authentication/con-understanding-authentication-and-user-provisioning.adoc @@ -2,8 +2,7 @@ = Understanding authentication and user provisioning -This module provides an overview of how authentication and user provisioning function within {product}. -Learn about the process from creating user and group entities in the software catalog to user sign-in, and how authentication and catalog plugins enable each step. +Learn about the authentication process from creating user and group entities in the software catalog to user sign-in, and how authentication and catalog plugins enable each step. Understanding this process is essential for successfully configuring your {product-short} instance, securing access through authorization, and enabling features that rely on synchronized user and group data. To fully enable catalog features, provision user and group data from the Identity Provider to the {product-short} software catalog. @@ -18,10 +17,14 @@ On successful authentication, the {product-short} authentication plugin, configu Configuring authentication and user provisioning is critical for several reasons. -* It secures your {product-short} instance by ensuring only authenticated users can gain access. -* It enables authorization by allowing you to define access controls based on user and group memberships synchronized from your IdP. +* Securing your {product-short} instance by ensuring only authenticated users can gain access. +* Enabling authorization by allowing you to define access controls based on user and group memberships synchronized from your IdP. * Provisioning user and group data to the catalog is necessary for various catalog features that rely on understanding entity ownership and relationships between users, groups, and software components. -Without this provisioning step, features like displaying who owns a component in the catalog may not function correctly. ++ +[IMPORTANT] +==== +Without this provisioning step, features such as displaying who owns a catalog entity might not function correctly. +==== [TIP] ==== @@ -29,10 +32,14 @@ To explore {product-short} features in a non-production environment, you can: * To use {product-short} without external IdP, enable the guest user to skip configuring authentication and authorization, log in as the guest user, and access all {product-short} features. -* To use {product-short} without authorization policies and features relying on the software catalog, you can enable the `dangerouslyAllowSignInWithoutUserInCatalog` resolver option. This setting bypasses the check requiring a user to be in the catalog but still enforces authentication. +* To use {product-short} without authorization policies and features relying on the software catalog, you can enable the `dangerouslyAllowSignInWithoutUserInCatalog` resolver option. +This setting bypasses the check requiring a user to be in the catalog but still enforces authentication. ==== [IMPORTANT] ==== -{product-short} uses a one-way synchronization model, where user and group data flow from your Identity Provider to the {product-short} software catalog. As a result, deleting users or groups manually through the {product-short} Web UI or REST API might be ineffective or cause inconsistencies, since those entities will be recreated during the next ingestion. +{product-short} uses a one-way synchronization model, where user and group data flow from your Identity Provider to the {product-short} software catalog. +As a result, +deleting users or groups manually through the {product-short} Web UI or REST API might be ineffective or cause inconsistencies, +since {product-short} will create those entities again during the next import. ==== diff --git a/modules/authentication/proc-authenticationg-with-the-guest-user-on-a-helm-based-installation.adoc b/modules/authentication/proc-authenticationg-with-the-guest-user-on-a-helm-based-installation.adoc index c4240c0d80..809b51390d 100644 --- a/modules/authentication/proc-authenticationg-with-the-guest-user-on-a-helm-based-installation.adoc +++ b/modules/authentication/proc-authenticationg-with-the-guest-user-on-a-helm-based-installation.adoc @@ -3,16 +3,15 @@ [id="authenticating-with-the-guest-user-on-a-helm-based-installation_{context}"] = Authenticating with the Guest user on a Helm-based installation -On a Helm-based installation, you can configure {product-short} to log in as a Guest user and access {product-short} features. +For trial or non-production environments installed by using the {product} Helm chart, you can enable guest access to skip configuring authentication and authorization and explore {product-short} features. .Prerequisites -* You {configuring-book-link}[added a custom {product-short} application configuration], and have sufficient permissions to modify it. +* You {configuring-book-link}[added a custom {product-short} application configuration], and have enough permissions to change it. * You {configuring-book-link}#using-the-helm-chart-to-run-rhdh-with-your-custom-configuration[use the {product} Helm chart to run {product-short}]. .Procedure -* To enable the guest user in your {product-short} custom configuration, {configuring-book-link}#using-the-helm-chart-to-run-rhdh-with-your-custom-configuration[configure your {product} Helm Chart] with following content: +* Add following content to your {product} Helm Chart: + -.{product} Helm Chart configuration fragment [source,yaml] ---- upstream: diff --git a/modules/authentication/proc-authenticationg-with-the-guest-user-on-an-operator-based-installation.adoc b/modules/authentication/proc-authenticationg-with-the-guest-user-on-an-operator-based-installation.adoc index a0d9175dce..50ddfe5aba 100644 --- a/modules/authentication/proc-authenticationg-with-the-guest-user-on-an-operator-based-installation.adoc +++ b/modules/authentication/proc-authenticationg-with-the-guest-user-on-an-operator-based-installation.adoc @@ -3,16 +3,15 @@ [id="authenticating-with-the-guest-user-on-an-operator-based-installation_{context}"] = Authenticating with the Guest user on an Operator-based installation -After an Operator-based installation, you can configure {product-short} to log in as a Guest user and access {product-short} features. +For trial or non-production environments installed by using the {product} Operator, you can enable guest access to skip configuring authentication and authorization and explore {product-short} features. .Prerequisites -* You {configuring-book-link}[added a custom {product-short} application configuration], and have sufficient permissions to modify it. +* You {configuring-book-link}[added a custom {product-short} application configuration], and have enough permissions to change it. * You {configuring-book-link}#using-the-operator-to-run-rhdh-with-your-custom-configurationn[use the {product} Operator to run {product-short}]. .Procedure -* To enable the guest user in your {product-short} custom configuration, {configuring-book-link}#provisioning-your-custom-configuration[edit your {product-short} application configuration] with following content: +* Add the following content to the `{my-app-config-file}` file: + -.`{my-app-config-file}` fragment [source,yaml] ---- auth: diff --git a/modules/authentication/proc-creating-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog.adoc b/modules/authentication/proc-creating-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog.adoc index 5fcf00d7c3..659e51ab43 100644 --- a/modules/authentication/proc-creating-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog.adoc +++ b/modules/authentication/proc-creating-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog.adoc @@ -3,7 +3,8 @@ [id="creating-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog"] = Creating a custom transformer to provision users from {rhbk-brand-name} ({rhbk}) to the software catalog -To customize how {rhbk} users and groups are mapped to {product} entities, you can create a backend module that uses the `keycloakTransformerExtensionPoint` to provide custom user and group transformers for the Keycloak backend. +Customize how {product} provisions users and groups to {product} software catalog entities, +by creating a backend module that uses the `keycloakTransformerExtensionPoint` to offer custom user and group transformers for the Keycloak backend. .Prerequisites * You have xref:enabling-user-authentication-with-rhbk[enabled provisioning users from {rhbk-brand-name} ({rhbk}) to the software catalog]. @@ -14,9 +15,8 @@ To customize how {rhbk} users and groups are mapped to {product} entities, you c . Add your custom user and group transformers to the `keycloakTransformerExtensionPoint`. + -The following is an example of how the backend module can be defined: +The following is an example `plugins/____/src/module.ts` file defining the backend module: + -.`plugins/____/src/module.ts` [source,javascript] ---- import { @@ -63,7 +63,7 @@ export const keycloakBackendModuleTransformer = createBackendModule({ + [IMPORTANT] ==== -The module's `pluginId` must be set to `catalog` to match the `pluginId` of the `keycloak-backend`; otherwise, the module fails to initialize. +Set the module's `pluginId` to `catalog` to match the `pluginId` of the `keycloak-backend`; otherwise, the module fails to initialize. ==== . Install this new backend module into your {product-short} backend. @@ -76,16 +76,17 @@ backend.add(import(backstage-plugin-catalog-backend-module-keycloak-transformer) .Verification * {product-short} imports the users and groups each time when started. -Check the console logs to verify that the synchronization is completed. +Check the console logs to verify the synchronization result. ++ +Successful synchronization example: + -.Successful synchronization example: [source,json] ---- {"class":"KeycloakOrgEntityProvider","level":"info","message":"Read 3 Keycloak users and 2 Keycloak groups in 1.5 seconds. Committing...","plugin":"catalog","service":"backstage","taskId":"KeycloakOrgEntityProvider:default:refresh","taskInstanceId":"bf0467ff-8ac4-4702-911c-380270e44dea","timestamp":"2024-09-25 13:58:04"} {"class":"KeycloakOrgEntityProvider","level":"info","message":"Committed 3 Keycloak users and 2 Keycloak groups in 0.0 seconds.","plugin":"catalog","service":"backstage","taskId":"KeycloakOrgEntityProvider:default:refresh","taskInstanceId":"bf0467ff-8ac4-4702-911c-380270e44dea","timestamp":"2024-09-25 13:58:04"} ---- -* After the first import is complete, navigate to the *Catalog* page and select **User** to view the list of users. +* After the first import is complete, go to the *Catalog* page and select **User** to view the list of users. * When you select a user, you see the information imported from {rhbk}. diff --git a/modules/authentication/proc-enabling-user-authentication-with-github.adoc b/modules/authentication/proc-enabling-user-authentication-with-github.adoc index a86fca6716..4743a717f6 100644 --- a/modules/authentication/proc-enabling-user-authentication-with-github.adoc +++ b/modules/authentication/proc-enabling-user-authentication-with-github.adoc @@ -3,12 +3,12 @@ [id="enabling-user-authentication-with-github"] = Enabling user authentication with GitHub -To authenticate users with GitHub, configure the GitHub authentication provider in {product} and provision the users and groups from GitHub to the {product-short} software catalog. +Authenticate users with GitHub by provisioning the users and groups from GitHub to the {product-short} software catalog, and configuring the GitHub authentication provider in {product}. .Prerequisites -* You {configuring-book-link}[added a custom {product-short} application configuration], and have sufficient permissions to modify it. +* You {configuring-book-link}[added a custom {product-short} application configuration], and have enough permissions to change it. -* You have sufficient permissions in GitHub to create and manage a link:https://docs.github.com/en/apps/overview[GitHub App]. +* You have enough permissions in GitHub to create and manage a link:https://docs.github.com/en/apps/overview[GitHub App]. Alternatively, you can ask your GitHub administrator to prepare the required GitHub App. .Procedure @@ -45,24 +45,25 @@ Select `Only on this account`. * **Client secret** . To add your GitHub credentials to {product-short}, add the following key/value pairs to {configuring-book-link}#provisioning-your-custom-configuration[your {product-short} secrets]. -You can use these secrets in the {product-short} configuration files by using their respective environment variable name. +You can use these secrets in the {product-short} configuration files by using their environment variable name. -`AUTHENTICATION_GITHUB_CLIENT_ID`:: +`GITHUB_CLIENT_ID`:: Enter the saved **Client ID**. -`AUTHENTICATION_GITHUB_CLIENT_SECRET`:: +`GITHUB_CLIENT_SECRET`:: Enter the saved **Client Secret**. -`AUTHENTICATION_GITHUB_HOST_DOMAIN`:: +`GITHUB_URL`:: Enter the GitHub host domain: `github.com`. -`AUTHENTICATION_GITHUB_ORGANIZATION`:: +`GITHUB_ORG`:: Enter your GitHub organization name, such as `____`. . Enable the GitHub organization provisioning plugin (`backstage-plugin-catalog-backend-module-github-org`). -This plugin ingests GitHub users and groups to the {product-short} software catalog. +This plugin imports GitHub users and groups to the {product-short} software catalog. ++ +`dynamic-plugins.yaml` file fragment: + -.`dynamic-plugins.yaml` file fragment [source,yaml] ---- plugins: @@ -70,19 +71,17 @@ plugins: disabled: false ---- -. To provision GitHub users and groups to the {product-short} software catalog, add the `catalog.providers.githubOrg` section to your custom {product-short} `{my-app-config-file}` configuration file: +. Provision GitHub users and groups to the {product-short} software catalog by adding the `catalog.providers.githubOrg` section to your custom {product-short} `{my-app-config-file}` configuration file: + --- [id=githubProviderId] -.`{my-app-config-file}` fragment with mandatory `catalog.providers.githubOrg` fields [source,yaml] ---- catalog: providers: githubOrg: id: githuborg - githubUrl: "${AUTHENTICATION_GITHUB_HOST_DOMAIN}" - orgs: [ "${AUTHENTICATION_GITHUB_ORGANIZATION}" ] + githubUrl: "${GITHUB_URL}" + orgs: [ "${GITHUB_ORG}" ] schedule: frequency: minutes: 30 @@ -94,13 +93,13 @@ catalog: `id`:: Enter a stable identifier for this provider, such as `githuborg`. -Entities from this provider are associated with this identifier, therefore you must take care not to change it over time since that might lead to orphaned entities and/or conflicts. +Entities from this provider are associated with this identifier, therefore you must take care not to change it over time since that might lead to orphaned entities or conflicts. `githubUrl`:: -Enter the configured secret variable name: `${AUTHENTICATION_GITHUB_HOST_DOMAIN}`. +Enter the configured secret variable name: `$\{GITHUB_URL}`. `orgs`:: -Enter the configured secret variable name: `${AUTHENTICATION_GITHUB_ORGANIZATION}`. +Enter the configured secret variable name: `$\{GITHUB_ORG}`. `schedule.frequency`:: Enter your schedule frequency, in the cron, ISO duration, or "human duration" format. @@ -110,12 +109,9 @@ Enter your schedule timeout, in the ISO duration or "human duration" format. `schedule.initialDelay`:: Enter your schedule initial delay, in the ISO duration or "human duration" format. --- -. To set up the GitHub authentication provider, add the `auth.providers.github` section to the `{my-app-config-file}` file content: +. To set up the GitHub authentication provider, add the `auth.providers.github` section to your `{my-app-config-file}` file: + --- -.`{my-app-config-file}` file fragment with mandatory fields to enable authentication with GitHub [source,yaml] ---- auth: @@ -123,8 +119,8 @@ auth: providers: github: production: - clientId: ${AUTHENTICATION_GITHUB_CLIENT_ID} - clientSecret: ${AUTHENTICATION_GITHUB_CLIENT_SECRET} + clientId: ${GITHUB_CLIENT_ID} + clientSecret: ${GITHUB_CLIENT_SECRET} signInPage: github ---- @@ -132,17 +128,19 @@ signInPage: github Enter `production` to disable the Guest login option in the {product-short} login page. `clientId`:: -Enter the configured secret variable name: `${AUTHENTICATION_GITHUB_CLIENT_ID}`. +Enter the configured secret variable name: `$\{GITHUB_CLIENT_ID}`. `clientSecret`:: -Enter the configured secret variable name: `${AUTHENTICATION_GITHUB_CLIENT_SECRET}`. +Enter the configured secret variable name: `$\{GITHUB_CLIENT_SECRET}`. `signInPage`:: Enter `github` to enable the GitHub provider as your {product-short} sign-in provider. - -Optional: Consider adding the following optional fields: - -.`{my-app-config-file}` file fragment including optional fields to enable authentication with GitHub ++ +Optional: Consider adding optional fields. +ifeval::["{optional-steps}" == "disable"] +See {configuring-book-link}[{configuring-book-title}]. +endif::[] +ifeval::["{optional-steps}" == "enable"] [source,yaml,subs="+quotes"] ---- auth: @@ -150,8 +148,8 @@ auth: providers: github: production: - clientId: ${AUTHENTICATION_GITHUB_CLIENT_ID} - clientSecret: ${AUTHENTICATION_GITHUB_CLIENT_SECRET} + clientId: ${GITHUB_CLIENT_ID} + clientSecret: ${GITHUB_CLIENT_SECRET} callbackUrl: ____ sessionDuration: { hours: 24 } signIn: @@ -178,7 +176,10 @@ Enter the resolver list to override the default resolver: `usernameMatchingUserE + The authentication provider tries each sign-in resolver in order until it succeeds, and fails if none succeed. + -WARNING: In production mode, only configure one resolver to ensure users are securely matched. +[WARNING] +==== +Make sure users are securely matched in production mode, by configuring only one resolver. +==== `resolver`:::: Enter the sign-in resolver name. @@ -191,13 +192,17 @@ Available resolvers: `dangerouslyAllowSignInWithoutUserInCatalog: true`:::: Configure the sign-in resolver to bypass the user provisioning requirement in the {product-short} software catalog. + -WARNING: Use `dangerouslyAllowSignInWithoutUserInCatalog` to explore {product-short} features, but do not use it in production. --- +[WARNING] +==== +Do not use `dangerouslyAllowSignInWithoutUserInCatalog` in production. Use this configuration only to explore {product-short} features. +==== +endif::[] .Verification -. To verify user and group provisioning, check the console logs. +. Verify user and group provisioning by checking the console logs. ++ +Successful synchronization example: + -.Successful synchronization example: [source,json] ---- {"class":"GithubMultiOrgEntityProvider","level":"info","message":"Reading GitHub users and teams for org: rhdh-dast","plugin":"catalog","service":"backstage","target":"https://github.com","taskId":"GithubMultiOrgEntityProvider:githuborg:refresh","taskInstanceId":"801b3c6c-167f-473b-b43e-e0b4b780c384","timestamp":"2024-09-09 23:55:58"} @@ -210,6 +215,5 @@ WARNING: Use `dangerouslyAllowSignInWithoutUserInCatalog` to explore {product-sh .. Log in with a GitHub account. .Additional resources - * {integrating-with-github-book-link}[{integrating-with-github-book-title}] diff --git a/modules/authentication/proc-enabling-user-authentication-with-microsoft-azure.adoc b/modules/authentication/proc-enabling-user-authentication-with-microsoft-azure.adoc index 3332ae82fe..9b3454ae56 100644 --- a/modules/authentication/proc-enabling-user-authentication-with-microsoft-azure.adoc +++ b/modules/authentication/proc-enabling-user-authentication-with-microsoft-azure.adoc @@ -3,21 +3,21 @@ [id="enabling-user-authentication-with-microsoft-azure"] = Enabling user authentication with {azure-brand-name} -To authenticate users with {azure-brand-name}, configure the {azure-short} authentication provider in {product} and provision the users and groups from {azure-short} to the {product-short} software catalog. +Authenticate users with {azure-brand-name} by provisioning the users and groups from {azure-short} to the {product-short} software catalog, and configuring the {azure-short} authentication provider in {product}. .Prerequisites * You have the permission to register an application in {azure-short}. Alternatively, you can ask your {azure-short} administrator to prepare the required {azure-short} application. -* You {configuring-book-link}[added a custom {product-short} application configuration], and have sufficient permissions to modify it. +* You {configuring-book-link}[added a custom {product-short} application configuration], and have enough permissions to change it. * Your {product-short} backend can access the following hosts: `login.microsoftonline.com`:: -This is the {azure-brand-name} authorization server, which enables the authentication flow. +The {azure-brand-name} authorization server, which enables the authentication flow. `graph.microsoft.com`:: -For retrieving organization data, including user and group data, to be ingested into the {product-short} catalog. +The server for retrieving organization data, including user and group data, to import into the {product-short} catalog. .Procedure :my-product-app-name-in-azure: @@ -25,7 +25,7 @@ For retrieving organization data, including user and group data, to be ingested .. Sign in to the link:https://entra.microsoft.com/[Microsoft Entra admin center]. -.. Optional: If you have access to multiple tenants, use the *Settings* icon in the top menu to switch to the tenant in which you want to register the application from the *Directories + subscriptions* menu. +.. Optional: If you have access to many tenants, use the *Settings* icon in the top menu to switch to the tenant in which you want to register the application from the *Directories + subscriptions* menu. .. Browse to *Applications > App registrations*, and create a **New registration** with the configuration: @@ -61,7 +61,7 @@ Delegated Permissions:: `profile`::: Enter permissions that enable authenticating users. + -Optional: Enter optional custom scopes for the Microsoft Graph API that you define both in this section and in the `{my-app-config-file}` {product-short} configuration file. +Optional: Enter optional custom scopes for the Microsoft Graph API that you define both here and in your `{my-app-config-file}` {product-short} configuration file. .. On the *Applications > App registrations > __{my-product-app-name-in-azure}__ > Manage > Certificates & secrets* page, in the *Client secrets* tab, create a *New client secret*. @@ -82,24 +82,20 @@ Enter your saved *Application (client) ID*. `AUTHENTICATION_AZURE_CLIENT_SECRET`:: Enter your saved *Application (client) secret*. -. Enable the Microsoft Graph organization provisioning plugin (`backstage-plugin-catalog-backend-module-msgraph-dynamic`). -This plugin ingests {azure-short} users and groups to the {product-short} software catalog. +. Enable the Microsoft Graph organization provisioning plugin (`backstage-plugin-catalog-backend-module-msgraph-dynamic`) in your `dynamic-plugins.yaml` +file. +This plugin imports {azure-short} users and groups to the {product-short} software catalog. + -.`dynamic-plugins.yaml` file fragment [source,yaml] ---- plugins: - package: './dynamic-plugins/dist/backstage-plugin-catalog-backend-module-msgraph-dynamic' disabled: false ---- -+ -include::{docdir}/artifacts/snip-technology-preview.adoc[] . To provision {azure-short} users and groups to the {product-short} software catalog, add the `catalog.providers.microsoftGraphOrg` section to your custom {product-short} `{my-app-config-file}` configuration file: + --- [id=microsoftGraphOrgProviderId] -.`{my-app-config-file}` fragment with mandatory `microsoftGraphOrg` fields [source,yaml] ---- catalog: @@ -124,13 +120,13 @@ Enter `\https://graph.microsoft.com/v1.0` to define the MSGraph API endpoint the You might change this parameter to use a different version, such as the link:https://learn.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta#call-the-beta-endpoint[beta endpoint]. `tenandId`:: -Enter the configured secret variable name: `${AUTHENTICATION_AZURE_TENANT_ID}`. +Enter the configured secret variable name: `$\{AUTHENTICATION_AZURE_TENANT_ID}`. `clientId`:: -Enter the configured secret variable name: `${AUTHENTICATION_AZURE_CLIENT_ID}`. +Enter the configured secret variable name: `$\{AUTHENTICATION_AZURE_CLIENT_ID}`. `clientSecret`:: -Enter the configured secret variable name: `${AUTHENTICATION_AZURE_CLIENT_SECRET}`. +Enter the configured secret variable name: `$\{AUTHENTICATION_AZURE_CLIENT_SECRET}`. `schedule`:: @@ -144,15 +140,16 @@ In a large organization, user provisioning might take a long time, therefore avo `initialDelay`::: Enter the schedule initial delay in the ISO duration or human duration format. - -Optional: Consider adding the following optional `microsoftGraphOrg.providerId` fields: - ++ +Optional: Consider adding optional fields. +ifeval::["{optional-steps}" == "disable"] +See {configuring-book-link}[{configuring-book-title}]. +endif::[] +ifeval::["{optional-steps}" == "enable"] [id=authority] `authority`:: -Enter your link:https://learn.microsoft.com/en-us/graph/deployments#app-registration-and-token-service-root-endpoints[{azure-short} authority URL], -when different from the default: `\https://login.microsoftonline.com`. +Enter your link:https://learn.microsoft.com/en-us/graph/deployments#app-registration-and-token-service-root-endpoints[{azure-short} authority URL] if it is different from the default: `\https://login.microsoftonline.com`. + -.`{my-app-config-file}` fragment with optional `queryMode` field [source,yaml] ---- catalog: @@ -164,10 +161,9 @@ catalog: [id=queryMode] `queryMode: basic | advanced`:: -Enter `advanced` when the default `basic` query mode is not sufficient for your queries to the Microsoft Graph API. +Enter `advanced` when the default `basic` query mode is insufficient for your queries to the Microsoft Graph API. See link:https://learn.microsoft.com/en-us/graph/aad-advanced-queries[{azure-brand-name} advanced queries]. + -.`{my-app-config-file}` fragment with optional `queryMode` field [source,yaml] ---- catalog: @@ -184,7 +180,6 @@ Only one relationship can be expanded in a single request. See https://learn.microsoft.com/en-us/graph/query-parameters#expand-parameter[Microsoft Graph query expand parameter]. This parameter can be combined with xref:userGroupMemberFilter[`userGroupMember.filter`] or xref:userFilter[`user.filter`]. + -.`{my-app-config-file}` fragment with optional `user.expand` field [source,yaml] ---- catalog: @@ -201,7 +196,6 @@ To filter users. See link:https://learn.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0#properties[Microsoft Graph API] and link:https://learn.microsoft.com/en-us/graph/query-parameters#filter-parameter[Microsoft Graph API query filter parameters syntax]. This parameter and xref:userGroupMemberFilter[`userGroupMember.filter`] are mutually exclusive, only one can be specified. + -.`{my-app-config-file}` fragment with optional `user.filter` field [source,yaml] ---- catalog: @@ -217,7 +211,6 @@ catalog: {product-short} loads photos by default. Enter `false` to avoid loading user photos. + -.`{my-app-config-file}` fragment with optional `user.loadPhotos` field [source,yaml] ---- catalog: @@ -232,7 +225,6 @@ catalog: `user.select`:: Enter the link:https://learn.microsoft.com/en-us/graph/api/resources/schemaextension?view=graph-rest-1.0[Microsoft Graph resource type] list to retrieve. + -.`{my-app-config-file}` fragment with optional `user.select` field [source,yaml] ---- catalog: @@ -249,7 +241,6 @@ To use group membership to get users. To filter groups and fetch their members. This parameter and xref:userFilter[`user.filter`] are mutually exclusive, only one can be specified. + -.`{my-app-config-file}` fragment with optional `userGroupMember.filter` field [source,yaml] ---- catalog: @@ -266,7 +257,6 @@ To use group membership to get users. To search for groups and fetch their members. This parameter and xref:userFilter[`user.filter`] are mutually exclusive, only one can be specified. + -.`{my-app-config-file}` fragment with optional `userGroupMember.search` field [source,yaml] ---- catalog: @@ -284,7 +274,6 @@ Only one relationship can be expanded in a single request. See link:https://learn.microsoft.com/en-us/graph/query-parameters#expand-parameter[Customize Microsoft Graph responses with query parameters]. This parameter can be combined with xref:userGroupMemberFilter[`userGroupMember.filter`] instead of xref:userFilter[`user.filter`]. + -.`{my-app-config-file}` fragment with optional `group.expand` field [source,yaml] ---- catalog: @@ -300,7 +289,6 @@ catalog: To filter groups. See link:https://learn.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties[Microsoft Graph API query group syntax]. + -.`{my-app-config-file}` fragment with optional `group.filter` field [source,yaml] ---- catalog: @@ -316,7 +304,6 @@ catalog: To search for groups. See link:https://learn.microsoft.com/en-us/graph/search-query-parameter[Microsoft Graph API query search parameter]. + -.`{my-app-config-file}` fragment with optional `group.search` field [source,yaml] ---- catalog: @@ -331,7 +318,6 @@ catalog: `group.select`:: Enter the link:https://learn.microsoft.com/en-us/graph/api/resources/schemaextension?view=graph-rest-1.0[Microsoft Graph resource type] list to retrieve. + -.`{my-app-config-file}` fragment with optional `group.select` field [source,yaml] ---- catalog: @@ -341,12 +327,10 @@ catalog: group: select: ['id', 'displayName', 'description'] ---- --- +endif::[] . To set up the {azure-short} authentication provider, add the `auth.providers.microsoft` section to your `{my-app-config-file}` file content: + --- -.`{my-app-config-file}` file fragment with mandatory fields to enable authentication with {azure-short} [source,yaml,subs="+quotes,+attributes"] ---- auth: @@ -364,20 +348,23 @@ signInPage: microsoft Enter `production` to disable the **Guest** login option in the {product-short} login page. `clientId`:: -Enter the configured secret variable name: `${AUTHENTICATION_AZURE_CLIENT_ID}`. +Enter the configured secret variable name: `$\{AUTHENTICATION_AZURE_CLIENT_ID}`. `clientSecret`:: Enter the configured secret variable name: -`${AUTHENTICATION_AZURE_CLIENT_SECRET}`. +`$\{AUTHENTICATION_AZURE_CLIENT_SECRET}`. `tenantId`:: -Enter the configured secret variable name: `${AUTHENTICATION_AZURE_TENANT_ID}`. +Enter the configured secret variable name: `$\{AUTHENTICATION_AZURE_TENANT_ID}`. `signInPage`:: Enter `microsoft` to set the {azure-short} provider as your {product-short} sign-in provider. - -Optional: Consider adding following optional fields: - ++ +Optional: Add optional fields. +ifeval::["{optional-steps}" == "disable"] +See {configuring-book-link}[{configuring-book-title}]. +endif::[] +ifeval::["{optional-steps}" == "enable"] `domainHint`:: Optional for single-tenant applications. You can reduce login friction for users with accounts in multiple tenants by automatically filtering out accounts from other tenants. @@ -385,7 +372,6 @@ If you want to use this parameter for a single-tenant application, uncomment and If your application registration is multi-tenant, leave this parameter blank. For more information, see link:https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/home-realm-discovery-policy[Home Realm Discovery]. + -.`{my-app-config-file}` file fragment with optional `domainHint` field [source,yaml,subs="+quotes,+attributes"] ---- auth: @@ -399,9 +385,13 @@ auth: `additionalScopes`:: Optional for additional scopes. To add scopes for the application registration, uncomment and enter the list of scopes that you want to add. -The default and mandatory value lists: `'openid', 'offline_access', 'profile', 'email', 'User.Read'`. +The default and mandatory value lists following scopes: +* `openid` +* `offline_access` +* `profile` +* `email` +* `User.Read` + -.`{my-app-config-file}` file fragment with optional `additionalScopes` field [source,yaml,subs="+quotes,+attributes"] ---- auth: @@ -417,7 +407,6 @@ auth: Lifespan of the user session. Enter a duration in `ms` library (such as '24h', '2 days'), ISO duration, or "human duration" format. + -.`app-config-rhdh.yaml` fragment with optional `sessionDuration` field [source,yaml,subs="+quotes"] ---- auth: @@ -440,7 +429,6 @@ The authentication provider tries each sign-in resolver in order until it succee + WARNING: In production mode, only configure one resolver to ensure users are securely matched. + -.`app-config-rhdh.yaml` fragment with optional field to allow signing in users absent from the software catalog [source,yaml] ---- auth: @@ -475,12 +463,12 @@ This resolver looks up the user by matching their Microsoft email to the user en Configure the sign-in resolver to bypass the user provisioning requirement in the {product-short} software catalog. + WARNING: Use `dangerouslyAllowSignInWithoutUserInCatalog` to explore {product-short} features, but do not use it in production. --- .Verification . To verify user and group provisioning, check the console logs for `MicrosoftGraphOrgEntityProvider` events. + -.Successful synchronization example: +Successful synchronization example: ++ [source] ---- 2025-06-23T13:37:55.804Z catalog info Read 9 msgraph users and 3 msgraph groups in 1.5 seconds. Committing... class="MicrosoftGraphOrgEntityProvider" taskId="MicrosoftGraphOrgEntityProvider:providerId:refresh" taskInstanceId="e104a116-6481-4ceb-9bc4-0f8f9581f959" trace_id="e4c633659cffd6b1529afa55a5bfbad7" span_id="76affd0420e8baa6" trace_flags="01" diff --git a/modules/authentication/proc-enabling-user-authentication-with-rhbk.adoc b/modules/authentication/proc-enabling-user-authentication-with-rhbk.adoc index 2259e4870e..fefdc4bb7f 100644 --- a/modules/authentication/proc-enabling-user-authentication-with-rhbk.adoc +++ b/modules/authentication/proc-enabling-user-authentication-with-rhbk.adoc @@ -2,11 +2,12 @@ [id="enabling-user-authentication-with-rhbk"] = Enabling user authentication with {rhbk-brand-name} ({rhbk}) -To authenticate users with {rhbk-brand-name} ({rhbk}), enable and configure the OpenID Connect (OIDC) authentication provider in {product} and provision the users and groups from {rhbk} to the {product-short} software catalog. +Authenticate users with {rhbk-brand-name} ({rhbk}), +by provisioning the users and groups from {rhbk} to the {product-short} software catalog, and configuring the OpenID Connect (OIDC) authentication provider in {product}. .Prerequisites -* You {configuring-book-link}[added a custom {product-short} application configuration], and have sufficient permissions to modify it. -* You have sufficient permissions in {rhsso} to create and manage a realm and a client. +* You {configuring-book-link}[added a custom {product-short} application configuration], and have enough permissions to change it. +* You have enough permissions in {rhsso} to create and manage a realm and a client. Alternatively, your {rhbk} administrator can prepare in {rhbk} the required realm and client for you. .Procedure @@ -19,7 +20,7 @@ Save the value for the next step: .. To register your {product-short} in {rhbk}, in the created realm, {rhbk-docs-link}/html-single/getting_started_guide/index#getting-started-zip-secure-the-first-application[secure the first application], with: ... **Client ID**: A distinctive client ID, such as __<{product-very-short}>__. ... **Valid redirect URIs**: Set to the OIDC handler URL: `pass:c,a,q[{my-product-url}/api/auth/oidc/handler/frame]`. -... Navigate to the **Credentials** tab and copy the **Client secret**. +... Go to the **Credentials** tab and copy the **Client secret**. ... Save the values for the next step: * **Client ID** * **Client Secret** @@ -27,22 +28,21 @@ Save the value for the next step: .. To prepare for the verification steps, in the same realm, get the credential information for an existing user or {rhbk-docs-link}/html-single/getting_started_guide/index#getting-started-zip-create-a-user[create a user]. Save the user credential information for the verification steps. . To add your {rhsso} credentials to {product-short}, add the following key/value pairs to {configuring-dynamic-plugins-book-link}#provisioning-your-custom-configuration[your {product-short} secrets]. -You can use these secrets in the {product-short} configuration files by using their respective environment variable name. +You can use these secrets in the {product-short} configuration files by using their environment variable name. + -`AUTHENTICATION_OIDC_CLIENT_ID`:: +`KEYCLOAK_CLIENT_ID`:: Enter the saved **Client ID**. -`AUTHENTICATION_OIDC_CLIENT_SECRET`:: +`KEYCLOAK_CLIENT_SECRET`:: Enter the saved **Client Secret**. -`AUTHENTICATION_OIDC_METADATA_URL`:: +`KEYCLOAK_BASE_URL`:: Enter the saved **{rhbk} realm base URL**. -. Enable the Keycloak organization plugin (`backstage-plugin-catalog-backend-module-keycloak-dynamic`). +. Enable the Keycloak organization plugin (`backstage-plugin-catalog-backend-module-keycloak-dynamic`) in your `dynamic-plugins.yaml` file. The plugin is named after {rhbk} upstream project. -This plugin ingests {rhbk} users and groups to the {product-short} software catalog. +This plugin imports {rhbk} users and groups to the {product-short} software catalog. + -.`dynamic-plugins.yaml` file fragment [source,yaml] ---- plugins: @@ -51,46 +51,45 @@ plugins: ---- . To provision {rhbk} users and groups to the {product-short} software catalog, add the `catalog.providers.keycloakOrg` section to your custom {product-short} `{my-app-config-file}` configuration file: - -.. Add mandatory fields: + [id=keycloakOrgProviderId] -.`{my-app-config-file}` fragment with mandatory `keycloakOrg` fields [source,yaml] ---- catalog: providers: keycloakOrg: default: - baseUrl: ${AUTHENTICATION_OIDC_METADATA_URL} - clientId: ${AUTHENTICATION_OIDC_CLIENT_ID} - clientSecret: ${AUTHENTICATION_OIDC_CLIENT_SECRET} + baseUrl: ${KEYCLOAK_BASE_URL} + clientId: ${KEYCLOAK_CLIENT_ID} + clientSecret: ${KEYCLOAK_CLIENT_SECRET} realm: master loginRealm: master ---- `baseUrl`:: -Enter your {rhbk} server URL, defined when xref:enabling-user-authentication-with-rhbk[enabling authentication with {rhbk}]. +Enter your {rhbk} server URL, defined earlier. `clientId`:: -Enter your {product-short} application client ID in {rhbk}, defined when xref:enabling-user-authentication-with-rhbk[enabling authentication with {rhbk}]. +Enter your {product-short} application client ID in {rhbk}, defined earlier. `clientSecret`:: -Enter your {product-short} application client secret in {rhbk}, defined when xref:enabling-user-authentication-with-rhbk[enabling authentication with {rhbk}]. +Enter your {product-short} application client secret in {rhbk}, defined earlier. `realm`:: Enter the realm name to provision users, such as `master`. `loginRealm`:: Enter the realm name to authenticate users, such as `master`. - -.. Optional: Consider adding optional fields: - ++ +Optional: Add optional fields. +ifeval::["{optional-steps}" == "disable"] +See {configuring-book-link}[{configuring-book-title}]. +endif::[] +ifeval::["{optional-steps}" == "enable"] `userQuerySize`:: Enter the user count to query simultaneously. Default value: `100`. + -.`{my-app-config-file}` fragment with optional `userQuerySize` field [source,yaml] ---- catalog: @@ -104,7 +103,6 @@ catalog: Enter the group count to query simultaneously. Default value: `100`. + -.`{my-app-config-file}` fragment with optional `groupQuerySize` field [source,yaml] ---- catalog: @@ -118,7 +116,6 @@ catalog: Enter the schedule frequency. Supports cron, ISO duration, and "human duration" as used in code. + -.`{my-app-config-file}` fragment with optional `schedule.frequency` field [source,yaml] ---- catalog: @@ -133,7 +130,6 @@ catalog: Enter the timeout for the user provisioning job. Supports ISO duration and "human duration" as used in code. + -.`{my-app-config-file}` fragment with optional `schedule.timeout` field [source,yaml] ---- catalog: @@ -148,7 +144,6 @@ catalog: Enter the initial delay to wait for before starting the user provisioning job. Supports ISO duration and "human duration" as used in code. + -.`{my-app-config-file}` fragment with optional `schedule.initialDelay` field [source,yaml] ---- catalog: @@ -158,12 +153,10 @@ catalog: schedule: initialDelay: { seconds: 15} ---- +endif::[] . To set up the {rhbk} authentication provider in your {product-short} custom configuration, edit your custom {product-short} ConfigMap such as `app-config-rhdh`, and add the following lines to the `{my-app-config-file}` content: - -.. Add mandatory fields: + -.`{my-app-config-file}` fragment with mandatory fields to enable authentication with {rhbk} [source,yaml] ---- auth: @@ -171,9 +164,9 @@ auth: providers: oidc: production: - metadataUrl: ${AUTHENTICATION_OIDC_METADATA_URL} - clientId: ${AUTHENTICATION_OIDC_CLIENT_ID} - clientSecret: ${AUTHENTICATION_OIDC_CLIENT_SECRET} + metadataUrl: ${KEYCLOAK_BASE_URL} + clientId: ${KEYCLOAK_CLIENT_ID} + clientSecret: ${KEYCLOAK_CLIENT_SECRET} prompt: auto signInPage: oidc ---- @@ -194,59 +187,58 @@ To allow the identity provider to automatically determine whether to prompt for ==== If `prompt: auto` is not set, the identity provider defaults to `prompt: none`, which assumes that you are already logged in and rejects sign-in requests without an active session. ==== - -.. Optional: Consider adding optional fields: - ++ +Optional: Add optional fields. +ifeval::["{optional-steps}" == "disable"] +See {configuring-book-link}[{configuring-book-title}]. +endif::[] +ifeval::["{optional-steps}" == "enable"] `callbackUrl`:: {rhbk} callback URL. + -.`{my-app-config-file}` fragment with optional `callbackURL` field [source,yaml] ---- auth: providers: oidc: production: - callbackUrl: ${AUTHENTICATION_OIDC_CALLBACK_URL} + callbackUrl: ${KEYCLOAK_CALLBACK_URL} ---- `tokenEndpointAuthMethod`:: Token endpoint authentication method. + -.`{my-app-config-file}` fragment with optional `tokenEndpointAuthMethod` field [source,yaml] ---- auth: providers: oidc: production: - tokenEndpointAuthMethod: ${AUTHENTICATION_OIDC_TOKEN_ENDPOINT_METHOD} + tokenEndpointAuthMethod: ${KEYCLOAK_TOKEN_ENDPOINT_METHOD} ---- `tokenSignedResponseAlg`:: Token signed response algorithm. + -.`{my-app-config-file}` fragment with optional `tokenSignedResponseAlg` field [source,yaml] ---- auth: providers: oidc: production: - tokenSignedResponseAlg: ${AUTHENTICATION_OIDC_SIGNED_RESPONSE_ALG} + tokenSignedResponseAlg: ${KEYCLOAK_SIGNED_RESPONSE_ALG} ---- `additionalScopes`:: Enter additional {rhbk} scopes to request for during the authentication flow. + -.`{my-app-config-file}` fragment with optional `additionalScopes` field [source,yaml] ---- auth: providers: oidc: production: - additionalScopes: ${AUTHENTICATION_OIDC_SCOPE} + additionalScopes: ${KEYCLOAK_SCOPE} ---- `signIn`:: @@ -273,9 +265,11 @@ Matches the preferred username with the user entity name. + The authentication provider tries each sign-in resolver in order until it succeeds, and fails if none succeed. + -WARNING: In production mode, only configure one resolver to ensure users are securely matched. +[WARNING] +==== +In production mode, only configure one resolver to ensure users are securely matched. +==== + -.`{my-app-config-file}` fragment with optional `resolvers` list [source,yaml] ---- auth: @@ -293,9 +287,11 @@ auth: `dangerouslyAllowSignInWithoutUserInCatalog: true`:::: Configure the sign-in resolver to bypass the user provisioning requirement in the {product-short} software catalog. + -WARNING: Use this option to explore {product-short} features, but do not use it in production. +[WARNING] +==== +Use this option to explore {product-short} features, but do not use it in production. +==== + -.`app-config-rhdh.yaml` fragment with optional field to allow signing in users absent from the software catalog [source,yaml] ---- auth: @@ -303,9 +299,9 @@ auth: providers: oidc: production: - metadataUrl: ${AUTHENTICATION_OIDC_METADATA_URL} - clientId: ${AUTHENTICATION_OIDC_CLIENT_ID} - clientSecret: ${AUTHENTICATION_OIDC_CLIENT_SECRET} + metadataUrl: ${KEYCLOAK_BASE_URL} + clientId: ${KEYCLOAK_CLIENT_ID} + clientSecret: ${KEYCLOAK_CLIENT_SECRET} signIn: resolvers: - resolver: oidcSubClaimMatchingKeycloakUserID @@ -317,7 +313,6 @@ signInPage: oidc Lifespan of the user session. Enter a duration in `ms` library format (such as '24h', '2 days'), ISO duration, or "human duration" as used in code. + -.`app-config-rhdh.yaml` fragment with optional `sessionDuration` field [source,yaml,subs="+quotes"] ---- auth: @@ -332,7 +327,6 @@ auth: `backstageTokenExpiration`::: To modify the {product-short} token expiration from its default value of one hour, note that this refers to the validity of short-term cryptographic tokens, not the session duration. The expiration value must be set between 10 minutes and 24 hours. + -.`{my-app-config-file}` fragment with optional `auth.backstageTokenExpiration` field [source,yaml,subs="+quotes"] ---- auth: @@ -347,12 +341,14 @@ For security, consider that if multiple valid refresh tokens are issued due to f . From the *Realm Settings* page, click the *Tokens* tab. . From the *Refresh tokens* section of the *Tokens* tab, toggle the *Revoke Refresh Token* to the *Enabled* position. ==== +endif::[] .Verification . To verify user and group provisioning, check the console logs. + -.Successful synchronization example: +Successful synchronization example: ++ [source] ---- 2025-06-27T16:02:34.647Z catalog info Read 5 Keycloak users and 3 Keycloak groups in 0.4 seconds. Committing... class="KeycloakOrgEntityProvider" taskId="KeycloakOrgEntityProvider:default:refresh" taskInstanceId="db55c34b-46b3-402b-b12f-2fbc48498e82" trace_id="606f80a9ce00d1c86800718c4522f7c6" span_id="7ebc2a254a546e90" trace_flags="01" diff --git a/titles/setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc b/titles/setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc index c0c66a8943..0a74e7ec89 100644 --- a/titles/setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc +++ b/titles/setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc @@ -3,8 +3,10 @@ include::artifacts/attributes.adoc[] :subtitle: Prepare your IT infrastructure including {ocp-brand-name} and required external components, and run your first {product} ({product-very-short}) instance in production. :abstract: Prepare your IT infrastructure including {ocp-brand-name} and required external components, and run your first {product} ({product-very-short}) instance in production. :context: setting-up-and-configuring-your-first-rhdh-instance +:optional-steps: disable [id="{context}"] = {title} {abstract} +include::assemblies/assembly-enabling-authentication-with-mandatory-steps-only.adoc[leveloffset=+1] From 01c6a09cd4ac8b81e998912775ac5a72746634b6 Mon Sep 17 00:00:00 2001 From: Judith Magak <124673476+jmagak@users.noreply.github.com> Date: Tue, 30 Sep 2025 10:59:51 +0200 Subject: [PATCH 09/22] RHIDP-7649: Incorrectly structured content related to the Topology plugin (#1418) * Update the incorrect structured content related to Topology Plugin (#1202) Co-authored-by: GitHub Actions * Topology modularization * Topology modularization * Topology modularization * Topology modularization --------- Co-authored-by: GitHub Actions --- .../assembly-configuring-rhdh-plugins.adoc | 13 ++++++++++--- ...nabling-the-bulk-import-from-github-feature.adoc | 2 +- ...edure-managing-the-imported-repository-list.adoc | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/assemblies/dynamic-plugins/assembly-configuring-rhdh-plugins.adoc b/assemblies/dynamic-plugins/assembly-configuring-rhdh-plugins.adoc index f6ec75f80a..9a812dad10 100644 --- a/assemblies/dynamic-plugins/assembly-configuring-rhdh-plugins.adoc +++ b/assemblies/dynamic-plugins/assembly-configuring-rhdh-plugins.adoc @@ -15,17 +15,24 @@ include::assembly-enabling-configuring-jfrog.adoc[leveloffset=+1] // Keycloak - modularized include::assembly-enabling-configuring-keycloak.adoc[leveloffset=+1] + // Nexus - modularized include::assembly-enabling-configuring-nexus.adoc[leveloffset=+1] // Tekton - modularized include::../../modules/dynamic-plugins/proc-enabling-the-tekton-plugin.adoc[leveloffset=+1] -// Topology - no-change -include::assembly-install-topology-plugin.adoc[leveloffset=+1] +// Topology +include::../dynamic-plugins/assembly-install-topology-plugin.adoc[leveloffset=+1] + +// Bulk Importing +include::../assembly-bulk-importing-from-github.adoc[leveloffset=+1] +// ServiceNow include::../assembly-using-servicenow.adoc[leveloffset=+1] +// Kubernetes Custom Actions include::../assembly-using-kubernetes-custom-actions.adoc[leveloffset=+1] -include::../../modules/dynamic-plugins/proc-overriding-core-backend-services.adoc[leveloffset=+1] +// Overriding Core Backend Service Configuration +include::../modules/dynamic-plugins/proc-overriding-core-backend-services.adoc[leveloffset=+1] diff --git a/modules/importing-repositories/procedure-enabling-the-bulk-import-from-github-feature.adoc b/modules/importing-repositories/procedure-enabling-the-bulk-import-from-github-feature.adoc index 97b22e44ba..cd908c0273 100644 --- a/modules/importing-repositories/procedure-enabling-the-bulk-import-from-github-feature.adoc +++ b/modules/importing-repositories/procedure-enabling-the-bulk-import-from-github-feature.adoc @@ -6,7 +6,7 @@ You can enable the Bulk Import feature for users and give them the necessary permissions to access it. .Prerequisites -* You have xref:enabling-github-repository-discovery[enabled GitHub repository discovery]. +* You have {integrating-with-github-book-link}#enabling-github-repository-discovery[enabled GitHub repository discovery]. .Procedure . The Bulk Import plugins are installed but disabled by default. diff --git a/modules/importing-repositories/procedure-managing-the-imported-repository-list.adoc b/modules/importing-repositories/procedure-managing-the-imported-repository-list.adoc index d80869b9a6..b2eae64aa1 100644 --- a/modules/importing-repositories/procedure-managing-the-imported-repository-list.adoc +++ b/modules/importing-repositories/procedure-managing-the-imported-repository-list.adoc @@ -26,7 +26,7 @@ Empty:: {product-short} is unable to determine the import job status because the [NOTE] ==== * After an import pull request is merged, the import status is marked as _Added_ in the list of Added Repositories, but it might take a few seconds for the corresponding entities to appear in the {product-short} Catalog. -* A location added through other sources (like statically in an `{my-app-config-file}` file, dynamically when xref:enabling-github-repository-discovery[enabling GitHub discovery], or registered manually using the "Register an existing component" page) might show up in the Bulk Import list of Added Repositories if the following conditions are met: +* A location added through other sources (like statically in an `{my-app-config-file}` file, dynamically when {integrating-with-github-book-link}#enabling-github-repository-discovery[enabling GitHub discovery], or registered manually using the "Register an existing component" page) might show up in the Bulk Import list of Added Repositories if the following conditions are met: ** The target repository is accessible from the configured GitHub integrations. ** The location URL points to a `catalog-info.yaml` file at the root of the repository default branch. ==== From fa76c6d8db18a159d8dd9187781108d9cf59ece0 Mon Sep 17 00:00:00 2001 From: Judith Magak <124673476+jmagak@users.noreply.github.com> Date: Tue, 7 Oct 2025 07:14:57 +0200 Subject: [PATCH 10/22] RHIDP-2628:Update installation prerequisite (#1365) * Update installation prerequisite * Minor fix * Minor fix --------- Co-authored-by: GitHub Actions --- modules/installation/proc-install-operator.adoc | 2 +- modules/installation/proc-install-rhdh-helm-airgapped-full.adoc | 2 +- .../installation/proc-install-rhdh-helm-airgapped-partial.adoc | 2 +- modules/installation/proc-install-rhdh-ocp-helm-gui.adoc | 2 +- .../installation/proc-install-rhdh-operator-airgapped-full.adoc | 2 +- .../proc-install-rhdh-operator-airgapped-partial.adoc | 2 +- modules/installation/proc-install-rhdh-osd-gcp-helm.adoc | 2 +- modules/installation/proc-install-rhdh-osd-gcp-operator.adoc | 2 +- modules/installation/proc-rhdh-deploy-aks-helm.adoc | 2 +- modules/installation/proc-rhdh-deploy-eks-helm.adoc | 2 +- modules/installation/proc-rhdh-deploy-gke-helm.adoc | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/installation/proc-install-operator.adoc b/modules/installation/proc-install-operator.adoc index 2a27a51312..2ab577ade7 100644 --- a/modules/installation/proc-install-operator.adoc +++ b/modules/installation/proc-install-operator.adoc @@ -14,7 +14,7 @@ Containers are available for the following CPU architectures: * You are logged in as an administrator on the {ocp-short} web console. * You have configured the appropriate roles and permissions within your project to create or access an application. For more information, see the {ocp-docs-link}/html-single/building_applications/index#building-applications-overview[{ocp-brand-name} documentation on Building applications]. * You have installed {ocp-brand-name} {ocp-version-min} to {ocp-version}. -* Make sure that your system meets the minimum sizing requirements. See link:https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.7/html-single/about_red_hat_developer_hub/index#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure diff --git a/modules/installation/proc-install-rhdh-helm-airgapped-full.adoc b/modules/installation/proc-install-rhdh-helm-airgapped-full.adoc index 4350cf146e..df5c7d79ef 100644 --- a/modules/installation/proc-install-rhdh-helm-airgapped-full.adoc +++ b/modules/installation/proc-install-rhdh-helm-airgapped-full.adoc @@ -18,7 +18,7 @@ If your network has access to the registry through a bastion host, you can use t ** {ocp-docs-link}/html-single/disconnected_environments/index#about-installing-oc-mirror-v2[You have installed the oc-mirror OpenShift CLI plugin]. ** You have installed {ocp-brand-name} {ocp-version-min} or later. ** You have installed the {openshift-cli} on your workstation. -* Make sure that your system meets the minimum sizing requirements. See link:https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.7/html-single/about_red_hat_developer_hub/index#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure . Create an `ImageSetConfiguration` file to specify the resources that you want to mirror. For example: diff --git a/modules/installation/proc-install-rhdh-helm-airgapped-partial.adoc b/modules/installation/proc-install-rhdh-helm-airgapped-partial.adoc index db023a0bee..f3771e4e9d 100644 --- a/modules/installation/proc-install-rhdh-helm-airgapped-partial.adoc +++ b/modules/installation/proc-install-rhdh-helm-airgapped-partial.adoc @@ -15,7 +15,7 @@ If your network has access to the `registry.redhat.io` registry and the `charts. * You have installed the {openshift-cli} on your workstation. * {ocp-docs-link}/html-single/disconnected_environments/index#about-installing-oc-mirror-v2[You have installed the oc-mirror OpenShift CLI plugin]. * You have an account in https://developers.redhat.com/[{rhdeveloper-name}] portal. -* Make sure that your system meets the minimum sizing requirements. See link:https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.7/html-single/about_red_hat_developer_hub/index#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure . Log in to your {ocp-short} account using the {openshift-cli} by running the following command: diff --git a/modules/installation/proc-install-rhdh-ocp-helm-gui.adoc b/modules/installation/proc-install-rhdh-ocp-helm-gui.adoc index 371804cd03..69b5223ba9 100644 --- a/modules/installation/proc-install-rhdh-ocp-helm-gui.adoc +++ b/modules/installation/proc-install-rhdh-ocp-helm-gui.adoc @@ -19,7 +19,7 @@ The {product} Helm chart is available in the Helm catalog on {osd-short} and {oc * You are logged in to your {ocp-short} account. * A user with the {ocp-short} `admin` role has configured the appropriate roles and permissions within your project to create an application. For more information about {ocp-short} roles, see {ocp-docs-link}/html-single/authentication_and_authorization/index#authorization-overview_using-rbac[Using RBAC to define and apply permissions]. * You have created a project in {ocp-short}. For more information about creating a project in {ocp-short}, see {ocp-docs-link}/html-single/building_applications/index#working-with-projects[{ocp-brand-name} documentation]. -* Make sure that your system meets the minimum sizing requirements. See link:https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.7/html-single/about_red_hat_developer_hub/index#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure diff --git a/modules/installation/proc-install-rhdh-operator-airgapped-full.adoc b/modules/installation/proc-install-rhdh-operator-airgapped-full.adoc index 546d2fa398..f266dfa906 100644 --- a/modules/installation/proc-install-rhdh-operator-airgapped-full.adoc +++ b/modules/installation/proc-install-rhdh-operator-airgapped-full.adoc @@ -16,7 +16,7 @@ If your network has access to the registry through a bastion host, you can use t * You have installed `umoci` CLI tool. * You have an active `oc registry`, `podman`, or `skopeo` session to the `registry.redhat.io` {company-name} Ecosystem Catalog. For more information, see link:link:https://access.redhat.com/articles/RegistryAuthentication[{company-name} Container Registry Authentication]. * You have installed the `opm` CLI tool. For more information, see {ocp-docs-link}/html/cli_tools/opm-cli#olm-about-opm_cli-opm-install[Installing the opm CLI]. -* Make sure that your system meets the minimum sizing requirements. See link:https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.7/html-single/about_red_hat_developer_hub/index#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure . Download the mirroring script to disk by running the following command: diff --git a/modules/installation/proc-install-rhdh-operator-airgapped-partial.adoc b/modules/installation/proc-install-rhdh-operator-airgapped-partial.adoc index e7495a980b..74f1e98203 100644 --- a/modules/installation/proc-install-rhdh-operator-airgapped-partial.adoc +++ b/modules/installation/proc-install-rhdh-operator-airgapped-partial.adoc @@ -28,7 +28,7 @@ When connected to a {ocp-short} cluster, the helper script detects it and automa * If you are using a supported Kubernetes cluster, you have the following prerequisites: ** You have installed the Operator Lifecycle Manager (OLM) on the disconnected cluster. ** You have a mirror registry that is reachable from the disconnected cluster. -* Make sure that your system meets the minimum sizing requirements. See link:https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.7/html-single/about_red_hat_developer_hub/index#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure . In your terminal, navigate to the directory where you want to save the mirroring script. diff --git a/modules/installation/proc-install-rhdh-osd-gcp-helm.adoc b/modules/installation/proc-install-rhdh-osd-gcp-helm.adoc index b9229718cb..dd31ed9e92 100644 --- a/modules/installation/proc-install-rhdh-osd-gcp-helm.adoc +++ b/modules/installation/proc-install-rhdh-osd-gcp-helm.adoc @@ -9,7 +9,7 @@ You can install {product-short} on {osd-short} on {gcp-short} using the {product * You have a valid {gcp-short} account. * Your {osd-short} cluster is running on {gcp-short}. For more information, see{osd-docs-link}/html/installing_accessing_and_deleting_openshift_dedicated_clusters/osd-creating-a-cluster-on-gcp[Creating a cluster on GCP] in {osd-brand-name} documentation. * You have installed Helm 3 or the latest. -* Make sure that your system meets the minimum sizing requirements. See link:https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.7/html-single/about_red_hat_developer_hub/index#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure . From the *Developer* perspective on the {product-short} web console, click *+Add*. diff --git a/modules/installation/proc-install-rhdh-osd-gcp-operator.adoc b/modules/installation/proc-install-rhdh-osd-gcp-operator.adoc index e9dd677b17..3e0cd70d9e 100644 --- a/modules/installation/proc-install-rhdh-osd-gcp-operator.adoc +++ b/modules/installation/proc-install-rhdh-osd-gcp-operator.adoc @@ -9,7 +9,7 @@ You can install {product-short} on {osd-short} on {gcp-short} using the {product * You have a valid {gcp-short} account. * Your {osd-short} cluster is running on {gcp-short}. For more information, see{osd-docs-link}/html/installing_accessing_and_deleting_openshift_dedicated_clusters/osd-creating-a-cluster-on-gcp[Creating a cluster on GCP] in {osd-brand-name} documentation. * You have administrator access to {osd-short} cluster and {gcp-short} project. -* Make sure that your system meets the minimum sizing requirements. See link:https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.7/html-single/about_red_hat_developer_hub/index#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure diff --git a/modules/installation/proc-rhdh-deploy-aks-helm.adoc b/modules/installation/proc-rhdh-deploy-aks-helm.adoc index fff9a67def..137ed300d4 100644 --- a/modules/installation/proc-rhdh-deploy-aks-helm.adoc +++ b/modules/installation/proc-rhdh-deploy-aks-helm.adoc @@ -12,7 +12,7 @@ You can deploy your {product-short} application on {aks-name} ({aks-short}) to a * You have installed the link:https://kubernetes.io/docs/reference/kubectl/[`kubectl` CLI]. * You are logged into your cluster using `kubectl`, and have `developer` or `admin` permissions. * You have installed Helm 3 or the latest. -* Make sure that your system meets the minimum sizing requirements. See link:https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.7/html-single/about_red_hat_developer_hub/index#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Comparison of {aks-short} specifics with the base {product-short} deployment diff --git a/modules/installation/proc-rhdh-deploy-eks-helm.adoc b/modules/installation/proc-rhdh-deploy-eks-helm.adoc index ac8c9b6c23..89afc6fffd 100644 --- a/modules/installation/proc-rhdh-deploy-eks-helm.adoc +++ b/modules/installation/proc-rhdh-deploy-eks-helm.adoc @@ -14,7 +14,7 @@ When you install the {product-short} Helm chart in {eks-name} ({eks-short}), it * You have set the context to the {eks-short} cluster in your current `kubeconfig`. For more information, see https://docs.aws.amazon.com/eks/latest/userguide/create-kubeconfig.html[Creating or updating a kubeconfig file for an Amazon {eks-short} cluster]. * You have installed `kubectl`. For more information, see https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html[Installing or updating kubectl]. * You have installed Helm 3 or the latest. For more information, see https://docs.aws.amazon.com/eks/latest/userguide/helm.html[Using Helm with Amazon {eks-short}]. -* Make sure that your system meets the minimum sizing requirements. See link:https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.7/html-single/about_red_hat_developer_hub/index#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure diff --git a/modules/installation/proc-rhdh-deploy-gke-helm.adoc b/modules/installation/proc-rhdh-deploy-gke-helm.adoc index 578fe3d148..a428d38785 100644 --- a/modules/installation/proc-rhdh-deploy-gke-helm.adoc +++ b/modules/installation/proc-rhdh-deploy-gke-helm.adoc @@ -13,7 +13,7 @@ When you install the {product-short} Helm chart in {gke-brand-name} ({gke-short} * You have configured a domain name for your {product-short} instance. * You have reserved a static external Premium IPv4 Global IP address that is not attached to any VM. For more information see https://cloud.google.com/vpc/docs/reserve-static-external-ip-address#reserve_new_static[Reserve a new static external IP address] * You have configured the DNS records for your domain name to point to the IP address that has been reserved. -* Make sure that your system meets the minimum sizing requirements. See link:https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.7/html-single/about_red_hat_developer_hub/index#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. + [NOTE] ==== From 3eb05bf54a14ea83fed6ee5cffec464970c2f906 Mon Sep 17 00:00:00 2001 From: Judith Magak <124673476+jmagak@users.noreply.github.com> Date: Tue, 7 Oct 2025 07:15:32 +0200 Subject: [PATCH 11/22] RHIDP-9106: Compatibility matrix (#1423) * Add the compatibility matrix * Add the compatibility matrix * Add the compatibility matrix * Add the compatibility matrix * Incorporate Piotr's suggestions * Minor update * Apply Jana's suggestion --------- Co-authored-by: GitHub Actions --- assemblies/assembly-orchestrator-rhdh.adoc | 3 +++ ...-compatibility-guide-for-orchestrator.adoc | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 modules/orchestrator/ref-compatibility-guide-for-orchestrator.adoc diff --git a/assemblies/assembly-orchestrator-rhdh.adoc b/assemblies/assembly-orchestrator-rhdh.adoc index 75ac57aa07..a1612f9678 100644 --- a/assemblies/assembly-orchestrator-rhdh.adoc +++ b/assemblies/assembly-orchestrator-rhdh.adoc @@ -29,6 +29,9 @@ To start using Orchestrator in {product-very-short}, you must: // orchestrator architecture include::modules/orchestrator/con-supported-architecture-for-orchestrator.adoc[leveloffset=+1] +// compatibility guide for {product} Orchestrator +include::modules/orchestrator/ref-compatibility-guide-for-orchestrator.adoc[leveloffset=+1] + // provisioning plugin dependencies include::modules/orchestrator/con-orchestrator-plugin-dependencies-operator.adoc[leveloffset=+1] diff --git a/modules/orchestrator/ref-compatibility-guide-for-orchestrator.adoc b/modules/orchestrator/ref-compatibility-guide-for-orchestrator.adoc new file mode 100644 index 0000000000..cb008b876c --- /dev/null +++ b/modules/orchestrator/ref-compatibility-guide-for-orchestrator.adoc @@ -0,0 +1,19 @@ +:_mod-docs-content-type: REFERENCE + +[id="con-compatibility-guide-for-orchestrator.adoc_{context}"] += Compatibility guide for Orchestrator + +The following table lists the {product-very-short} Orchestrator plugin versions and their compatible corresponding infrastructure versions. + +[cols="2,2,2,2,2"] +|=== +| Orchestrator plugin version | {product} ({product-very-short}) version | OpenShift version | OpenShift Serverless Logic (OSL) version | OpenShift Serverless version +| Orchestrator `1.5` | `1.5` | `4.14` - `4.18` | OSL `1.35` | `1.35` +| Orchestrator `1.6` | `1.6` | `4.14` - `4.18` | OSL `1.36` | `1.36` +| Orchestrator `1.7` ({product-very-short} {product-version}) | {product-version} | `4.16` - `4.19` | OSL `1.36` | `1.36` +|=== + +[NOTE] +==== +Orchestrator plugin supports the same {ocp-short} versions as {product-very-short}. See the link:https://access.redhat.com/support/policy/updates/developerhub?extIdCarryOver=true&sc_cid=RHCTG0180000371695[Life Cycle] page. +==== \ No newline at end of file From 5b91c654776fd9f46a39514053d87aca09ddee6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20Flore-Th=C3=A9bault?= Date: Tue, 7 Oct 2025 14:06:15 +0200 Subject: [PATCH 12/22] RHIDP-7849 Added existing modules to _Setting up and configuring your first instance_ (#1267) Co-authored-by: Jana Vrbkova --- assemblies/assembly-configuring-a-proxy.adoc | 3 + ...iguring-external-postgresql-databases.adoc | 3 + ...ssembly-configuring-high-availability.adoc | 4 + ...y-provisioning-a-custom-configuration.adoc | 3 + ...r-authentication-with-microsoft-azure.adoc | 3 +- ...our-first-rhdh-instance-in-production.adoc | 24 ++++ ...-rhdh-instance-with-tls-in-kubernetes.adoc | 2 +- ...proc-preparing-your-external-services.adoc | 118 ++++++++++++++++++ ...rovisioning-your-custom-configuration.adoc | 89 +++++++------ ...figuration-in-getting-started-context.adoc | 74 +++++++++++ ...fig-step-with-optional-steps-disabled.adoc | 70 +++++++++++ ...nfig-step-with-optional-steps-enabled.adoc | 35 ++++++ ...uration-next-steps-in-default-context.adoc | 4 + ...ring-your-first-rhdh-instance-context.adoc | 6 + ...ring-your-first-rhdh-instance-context.adoc | 25 ++++ ...tion-secrets-steps-in-default-context.adoc | 1 + ...ring-your-first-rhdh-instance-context.adoc | 53 ++++++++ .../installation/proc-install-operator.adoc | 5 - titles/configuring/master.adoc | 4 +- titles/install-rhdh-air-gapped/master.adoc | 1 + titles/install-rhdh-aks/master.adoc | 1 + titles/install-rhdh-eks/master.adoc | 1 + titles/install-rhdh-gke/master.adoc | 1 + .../master.adoc | 21 ++++ 24 files changed, 501 insertions(+), 50 deletions(-) create mode 100644 modules/configuring/con-checklist-to-run-your-first-rhdh-instance-in-production.adoc rename modules/{installation => configuring}/proc-configuring-an-rhdh-instance-with-tls-in-kubernetes.adoc (98%) create mode 100644 modules/configuring/proc-preparing-your-external-services.adoc create mode 100644 modules/configuring/proc-using-the-operator-to-run-rhdh-with-your-custom-configuration-in-getting-started-context.adoc create mode 100644 modules/configuring/snip-provisioning-your-custom-configuration-appconfig-step-with-optional-steps-disabled.adoc create mode 100644 modules/configuring/snip-provisioning-your-custom-configuration-appconfig-step-with-optional-steps-enabled.adoc create mode 100644 modules/configuring/snip-provisioning-your-custom-configuration-next-steps-in-default-context.adoc create mode 100644 modules/configuring/snip-provisioning-your-custom-configuration-next-steps-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc create mode 100644 modules/configuring/snip-provisioning-your-custom-configuration-prerequisites-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc create mode 100644 modules/configuring/snip-provisioning-your-custom-configuration-secrets-steps-in-default-context.adoc create mode 100644 modules/configuring/snip-provisioning-your-custom-configuration-secrets-steps-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc diff --git a/assemblies/assembly-configuring-a-proxy.adoc b/assemblies/assembly-configuring-a-proxy.adoc index 4581b84d1d..885a36b0b6 100644 --- a/assemblies/assembly-configuring-a-proxy.adoc +++ b/assemblies/assembly-configuring-a-proxy.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +:previouscontext: {context} :context: running-behind-a-proxy [id="{context}"] @@ -22,3 +23,5 @@ include::modules/configuring-a-proxy/proc-configuring-proxy-in-operator-deployme include::modules/configuring-a-proxy/proc-configuring-proxy-in-helm-deployment.adoc[leveloffset=+1] +:context: {previouscontext} +!:previouscontext: diff --git a/assemblies/assembly-configuring-external-postgresql-databases.adoc b/assemblies/assembly-configuring-external-postgresql-databases.adoc index 2868f420b1..72b6e2549f 100644 --- a/assemblies/assembly-configuring-external-postgresql-databases.adoc +++ b/assemblies/assembly-configuring-external-postgresql-databases.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +:previouscontext: {context} :context: configuring-external-postgresql-databases [id="{context}"] @@ -22,3 +23,5 @@ include::modules/configuring-external-databases/proc-configuring-postgresql-inst include::modules/configuring-external-databases/proc-migrating-databases-to-an-external-server.adoc[leveloffset=+1] +:context: {previouscontext} +!:previouscontext: diff --git a/assemblies/assembly-configuring-high-availability.adoc b/assemblies/assembly-configuring-high-availability.adoc index 5997b9eefd..034cb12c2c 100644 --- a/assemblies/assembly-configuring-high-availability.adoc +++ b/assemblies/assembly-configuring-high-availability.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +:previouscontext: {context} :context: HighAvailability [id="{context}"] @@ -28,3 +29,6 @@ As an administrator, you can configure high availability by adjusting replica va include::modules/configuring-high-availability/proc-configuring-high-availability-in-rhdh-operator-deployment.adoc[leveloffset=+1] include::modules/configuring-high-availability/proc-configuring-high-availability-in-rhdh-helm-chart-deployment.adoc[leveloffset=+1] + +:context: {previouscontext} +!:previouscontext: diff --git a/assemblies/assembly-provisioning-a-custom-configuration.adoc b/assemblies/assembly-provisioning-a-custom-configuration.adoc index 597286d49a..d77f7ad440 100644 --- a/assemblies/assembly-provisioning-a-custom-configuration.adoc +++ b/assemblies/assembly-provisioning-a-custom-configuration.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +:previouscontext: {context} :context: provisioning-and-using-your-custom-configuration [id="{context}"] @@ -27,3 +28,5 @@ include::modules/configuring/proc-mounting-additional-files-in-your-custom-confi include::modules/configuring/proc-using-the-helm-chart-to-run-rhdh-with-your-custom-configuration.adoc[leveloffset=+1] +:context: {previouscontext} +!:previouscontext: diff --git a/modules/authentication/proc-enabling-user-authentication-with-microsoft-azure.adoc b/modules/authentication/proc-enabling-user-authentication-with-microsoft-azure.adoc index 9b3454ae56..f59c7f15e2 100644 --- a/modules/authentication/proc-enabling-user-authentication-with-microsoft-azure.adoc +++ b/modules/authentication/proc-enabling-user-authentication-with-microsoft-azure.adoc @@ -385,7 +385,7 @@ auth: `additionalScopes`:: Optional for additional scopes. To add scopes for the application registration, uncomment and enter the list of scopes that you want to add. -The default and mandatory value lists following scopes: +The default and mandatory value lists following scopes: * `openid` * `offline_access` * `profile` @@ -463,6 +463,7 @@ This resolver looks up the user by matching their Microsoft email to the user en Configure the sign-in resolver to bypass the user provisioning requirement in the {product-short} software catalog. + WARNING: Use `dangerouslyAllowSignInWithoutUserInCatalog` to explore {product-short} features, but do not use it in production. +endif::[] .Verification . To verify user and group provisioning, check the console logs for `MicrosoftGraphOrgEntityProvider` events. diff --git a/modules/configuring/con-checklist-to-run-your-first-rhdh-instance-in-production.adoc b/modules/configuring/con-checklist-to-run-your-first-rhdh-instance-in-production.adoc new file mode 100644 index 0000000000..7bfbf66aaa --- /dev/null +++ b/modules/configuring/con-checklist-to-run-your-first-rhdh-instance-in-production.adoc @@ -0,0 +1,24 @@ +:_mod-docs-content-type: CONCEPT + +[id="checklist-to-run-your-first-rhdh-instance-in-production_{context}"] += Checklist to run your first {product} ({product-very-short}) instance in production. + +With the default configuration, {product-short} runs with a minimal feature set that does not require secure connection to external services such as an identity provider, a Git provider, and external PostgreSQL and Redis databases. + +Using critical features therefore requires following additional configuration: + +For resiliency:: +* Use an external PostgreSQL database. +* Enable high-availability. + +For performance:: +* Enable assets caching to an external Redis database. + +For security:: +* Use secure connections to your external services. +* Provision users and enable authentication. +* Enable role-based access control, and configure the permission policy by using the Web UI. + +For adapting to your environment:: +* Enable GitHub repository discovery. +* Customize {product-short} appearance with your logo. diff --git a/modules/installation/proc-configuring-an-rhdh-instance-with-tls-in-kubernetes.adoc b/modules/configuring/proc-configuring-an-rhdh-instance-with-tls-in-kubernetes.adoc similarity index 98% rename from modules/installation/proc-configuring-an-rhdh-instance-with-tls-in-kubernetes.adoc rename to modules/configuring/proc-configuring-an-rhdh-instance-with-tls-in-kubernetes.adoc index 8e8c6a0405..77186b221c 100644 --- a/modules/installation/proc-configuring-an-rhdh-instance-with-tls-in-kubernetes.adoc +++ b/modules/configuring/proc-configuring-an-rhdh-instance-with-tls-in-kubernetes.adoc @@ -1,6 +1,6 @@ :_mod-docs-content-type: PROCEDURE -[id="proc-configuring-an-rhdh-instance-with-tls-in-kubernetes_{context}"] +[id="configuring-an-rhdh-instance-with-tls-in-kubernetes_{context}"] = Configuring an {product-very-short} instance with a TLS connection in Kubernetes You can configure a {product-very-short} instance with a Transport Layer Security (TLS) connection in a Kubernetes cluster, such as an Azure Red Hat OpenShift (ARO) cluster, any cluster from a supported cloud provider, or your own cluster with proper configuration. Transport Layer Security (TLS) ensures a secure connection for the {product-very-short} instance with other entities, such as third-party applications, or external databases. However, you must use a public Certificate Authority (CA)-signed certificate to configure your Kubernetes cluster. diff --git a/modules/configuring/proc-preparing-your-external-services.adoc b/modules/configuring/proc-preparing-your-external-services.adoc new file mode 100644 index 0000000000..4da7ba2faf --- /dev/null +++ b/modules/configuring/proc-preparing-your-external-services.adoc @@ -0,0 +1,118 @@ +:_mod-docs-content-type: PROCEDURE + +[id="preparing-your-external-services"] += Preparing your external services + +{product} relies on external services. +Prepare the following required external services: + +PostgreSQL database:: +{product-short} stores data in a PostgreSQL database. +Use an external database for resiliency and include it in your disaster recovery plan. + +Redis cache:: +For efficiency, {product-short} caches plugin and Techdocs assets when your provide a Redis cache server. + +GitHub API access:: +Provide credentials to a GitHub app to enable access to the GitHub API for repository discovery. + +Connection to your identity provider:: +Provide credentials to your identity provider to enable user provisioning and authentication. + +.Procedure +* Get your external PostgreSQL database connection strings and certificates. +postgres-host::: Your PostgreSQL instance Domain Name System (DNS) or IP address. +postgres-port::: Your PostgreSQL instance port number, such as 5432. +postres-username::: The user name to connect to your PostgreSQL instance. +postgres-password::: The password to connect to your PostgreSQL instance. +postgres-ca.pem::: +postgres-key.key::: +postgres-crt.pem::: +For security, use TLS certificates to secure the connection to the database. + +. Get your Redis cache server connection string, such as `rediss://user:pass@cache.example.com:6379`. +For security, consider using a `rediss` secure server connection. + +. Create a GitHub App to allow {product-short} to access the GitHub API for repository. +Opt for a GitHub App instead of an OAuth app to use fine-grained permissions, gain more control over which repositories the application can access, and use short-lived tokens. + +.. link:https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app[Register a GitHub App] with the following configuration: + +GitHub App name:: +Enter a unique name identifying your GitHub App, such as `integrating-with-rhdh-____`. + +Homepage URL:: +Enter your {product-short} URL: `pass:c,a,q[{my-product-url}]`. + +Authorization callback URL:: +Enter your {product-short} authentication backend URL: `pass:c,a,q[{my-product-url}/api/auth/github/handler/frame]`. + +Webhook:: +Clear "Active", as this is not needed for authentication and catalog providers. + +App permissions:: +Select permissions to define the level of access for the app. +Adapt permissions to your needs: + +Reading software components::: + +Contents:::: +`Read-only` + +Commit statuses:::: +`Read-only` + +Reading organization data::: + +Members:::: +`Read-only` + +Publishing software templates::: +Set permissions if you intend to use the same GitHub App for software templates. + +Administration:::: +`Read & write` (for creating repositories) + +Contents:::: +`Read & write` + +Metadata:::: +`Read-only` + +Pull requests:::: +`Read & write` + +Issues:::: +`Read & write` + +Workflows:::: +`Read & write` (if templates include GitHub workflows) + +Variables:::: +`Read & write` (if templates include GitHub Action Repository Variables) + +Secrets:::: +`Read & write` (if templates include GitHub Action Repository Secrets) + +Environments:::: +`Read & write` (if templates include GitHub Environments) + +Organization permissions:: +Members::: +`Read-only` + +Where can this GitHub App be installed?:: +Select `Only on this account`. + +.. In the *General* -> *Clients secrets* section, click *Generate a new client secret*. + +.. In the *General* -> *Private keys* section, click *Generate a private key*. + +.. In the *Install App* tab, choose an account to install your GitHub App on. + +.. Save the following values for the next step: + +* **App ID** +* **Client ID** +* **Client secret** +* **Private key** diff --git a/modules/configuring/proc-provisioning-your-custom-configuration.adoc b/modules/configuring/proc-provisioning-your-custom-configuration.adoc index 840cae0479..bb6e6e293c 100644 --- a/modules/configuring/proc-provisioning-your-custom-configuration.adoc +++ b/modules/configuring/proc-provisioning-your-custom-configuration.adoc @@ -14,50 +14,48 @@ Your changes on this configuration might get reverted on {product-short} restart .Prerequisites * By using the {platform-cli-link}, you have access, with developer permissions, to the {platform-generic} cluster aimed at containing your {product-short} instance. +ifeval::["{context}" == "setting-up-and-configuring-your-first-rhdh-instance"] +include::snip-provisioning-your-custom-configuration-prerequisites-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc[] +endif::[] + .Procedure -. Author your custom `__.txt` file to provision your secrets as environment variables values in {a-platform-generic} secret, -rather than in clear text in your configuration files. -It contains one secret per line in `KEY=value` form. +. For security, store your secrets as environment variables values in an {ocp-short} secret, rather than in clear text in your configuration files. +Collect all your secrets in the `secrets.txt` file, with one secret per line in `KEY=value` form. + -* {authentication-book-link}[Enter your authentication secrets]. +-- +ifeval::["{context}" == "setting-up-and-configuring-your-first-rhdh-instance"] +include::snip-provisioning-your-custom-configuration-secrets-steps-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc[] +endif::[] +ifeval::["{context}" != "setting-up-and-configuring-your-first-rhdh-instance"] +include::snip-provisioning-your-custom-configuration-secrets-steps-in-default-context.adoc[] +endif::[] +-- . Author your custom `{my-app-config-file}` file. This is the main {product-short} configuration file. You need a custom `{my-app-config-file}` file to avoid the {product-short} installer to revert user edits during upgrades. When your custom `{my-app-config-file}` file is empty, {product-short} is using default values. ++ +-- +include::snip-provisioning-your-custom-configuration-appconfig-step-with-optional-steps-{optional-steps}d.adoc[] +-- -** To prepare a deployment with the {product} Operator on {platform}, you can start with an empty file. - -** To prepare a deployment with the {product} Helm chart, or on Kubernetes, enter the {product-short} base URL in the relevant fields in your `{my-app-config-file}` file to ensure proper functionality of {product-short}. -The base URL is what a {product-short} user sees in their browser when accessing {product-short}. -The relevant fields are `baseUrl` in the `app` and `backend` sections, and `origin` in the `backend.cors` subsection: +. Author your custom `dynamic-plugins.yaml` file to enable plugins. +By default, {product-short} enables a minimal plugin set, and disables plugins that require configuration or secrets, such as the GitHub repository discovery plugin and the Role-based access control (RBAC) plugin. + -.Configuring the `baseUrl` in `{my-app-config-file}` -==== -[source,yaml,subs="+attributes,+quotes"] +Enable the GitHub repository discovery and the RBAC features: ++ +.`dynamic.plugins.yaml` +[source,yaml] ---- -app: - title: {product} - baseUrl: {my-product-url} - -backend: - auth: - externalAccess: - - type: legacy - options: - subject: legacy-default-config - secret: "${BACKEND_SECRET}" - baseUrl: {my-product-url} - cors: - origin: {my-product-url} +includes: + - dynamic-plugins.default.yaml +plugins: + - package: ./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-github + disabled: false + - package: ./dynamic-plugins/dist/backstage-community-plugin-rbac + disabled: false ---- -==== - -** Optionally, enter your configuration such as: - -*** {authentication-book-link}[{authentication-book-title}]. -*** {authorization-book-link}[{authorization-book-title}]. -*** {customizing-book-link}[Customization]. . Provision your custom configuration files to your {platform} cluster. @@ -68,21 +66,30 @@ backend: $ oc create namespace {my-product-namespace} ---- -.. Provision your `{my-app-config-file}` file to the `{my-app-config-config-map}` config map in the _<{my-product-namespace}>_ project. +.. Provision your `{my-app-config-file}` and `dynamic-plugins.yaml` files respectively to the `{my-app-config-config-map}`, and `dynamic-plugins-rhdh` config maps in the _<{my-product-namespace}>_ project. + [source,terminal,subs="+attributes,+quotes"] ---- $ oc create configmap {my-app-config-config-map} --from-file={my-app-config-file} --namespace={my-product-namespace} +$ oc create configmap dynamic-plugins-rhdh --from-file=dynamic-plugins.yaml --namespace={my-product-namespace} ---- ++ +Alternatively, +link:https://docs.redhat.com/en/documentation/openshift_container_platform/{ocp-version}/html-single/nodes/index#nnodes-pods-configmap-create-from-console_configmaps[create the config maps by using the web console]. -.. Provision your `__.txt` file to the `__` secret in the _<{my-product-namespace}>_ project. +.. Provision your `secrets.txt` file to the `{my-product-secrets}` secret in the _<{my-product-namespace}>_ project. + [source,terminal,subs="+attributes,+quotes"] ---- -$ oc create secret generic `__` --from-file=`__.txt` --namespace={my-product-namespace} +$ oc create secret generic {my-product-secrets} --from-file=secrets.txt --namespace={my-product-namespace} ---- - -.Next steps -* To use an external PostgreSQL database, {configuring-book-link}configuring-external-postgresql-databases[provision your PostgreSQL database secrets]. -* To enable dynamic plugins, {installing-and-viewing-plugins-book-link}[provision your dynamic plugins config map]. -* To configure authorization by using external files, {authorization-book-link}#managing-authorizations-by-using-external-files[provision your RBAC policies config map]. ++ +Alternatively, +link:https://docs.redhat.com/en/documentation/openshift_container_platform/{ocp-version}/html-single/nodes/index#nodes-pods-secrets-creating-web-console-secrets_nodes-pods-secrets[create the secret by using the web console]. + +ifeval::["{context}" == "setting-up-and-configuring-your-first-rhdh-instance"] +include::snip-provisioning-your-custom-configuration-next-steps-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc[] +endif::[] +ifeval::["{context}" != "setting-up-and-configuring-your-first-rhdh-instance"] +include::snip-provisioning-your-custom-configuration-next-steps-in-default-context.adoc[] +endif::[] diff --git a/modules/configuring/proc-using-the-operator-to-run-rhdh-with-your-custom-configuration-in-getting-started-context.adoc b/modules/configuring/proc-using-the-operator-to-run-rhdh-with-your-custom-configuration-in-getting-started-context.adoc new file mode 100644 index 0000000000..c52da081be --- /dev/null +++ b/modules/configuring/proc-using-the-operator-to-run-rhdh-with-your-custom-configuration-in-getting-started-context.adoc @@ -0,0 +1,74 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-configuring-an-rhdh-instance-with-tls-in-kubernetes_{context}"] +[id="using-the-operator-to-run-rhdh-with-your-custom-configuration"] += Using the {product} Operator to run {product-short} with your custom configuration + +Use the {product-short} Operator to run {product} with your custom configuration by creating your {product-custom-resource-type} custom resource (CR) that can perform the following actions: + +* Mount files provisioned in your custom config maps. +* Inject environment variables provisioned in your custom secrets. + +.Prerequisites +* By using the link:https://docs.redhat.com/en/documentation/openshift_container_platform/{ocp-version}/html-single/cli_tools/index#cli-about-cli_cli-developer-commands[{openshift-cli}], you have access, with developer permissions, to the {ocp-short} cluster aimed at containing your {product-short} instance. +* xref:proc-install-operator_setting-up-and-configuring-your-first-rhdh-instance[] +* xref:provisioning-your-custom-configuration[] + +.Procedure + +. Author your {product-custom-resource-type} CR in a `{my-product-cr-name}.yaml` file to use your custom config maps and secrets. ++ +.`{my-product-cr-name}.yaml` custom resource example with dynamic plugins and RBAC policies config maps, and external PostgreSQL database secrets. +[source,yaml,subs="+attributes,+quotes"] +---- +apiVersion: rhdh.redhat.com/v1alpha3 +kind: Backstage +metadata: + name: _<{my-product-cr-name}>_ +spec: + application: + appConfig: + mountPath: /opt/app-root/src + configMaps: + - name: {my-app-config-config-map} + - name: rbac-policies + dynamicPluginsConfigMapName: dynamic-plugins-rhdh + extraEnvs: + envs: + - name: HTTP_PROXY + value: 'http://10.10.10.105:3128' + - name: HTTPS_PROXY + value: 'http://10.10.10.106:3128' + - name: NO_PROXY + value: 'localhost,example.org' + secrets: + - name: {my-product-secrets} + extraFiles: + mountPath: /opt/app-root/src + secrets: + - name: {my-product-database-certificates-secrets} + key: postgres-crt.pem, postgres-ca.pem, postgres-key.key + replicas: 2 + database: + enableLocalDb: false +---- + +`application`:: +`appConfig`::: Register your `{my-app-config-config-map}` and `rbac-policies` config maps. +`dynamicPluginsConfigMapName`::: Register your `dynamic-plugins-rhdh` config map. +`extraEnvs`::: +`env`:::: Enter your proxy environment variables. +`secrets`:::: Register your `` and `{my-product-database-secrets}` secrets. +`extraFiles`::: +`secrets`:::: +Register the `postgres-crt.pem`, `postgres-ca.pem`, and `postgres-key.key` files contained in the `{my-product-database-certificates-secrets}` secret. +`replicas`::: Enable high availability (HA) by increasing the replicas count to a value higher or equal to 2. +`database`:: +`enableLocalDb`::: Use your external PostgreSQL database rather than the internal PostgreSQL database. + +. Apply your {product-custom-resource-type} CR to start or update your {product-short} instance. ++ +[source,terminal,subs="+attributes,+quotes"] +---- +$ oc apply --filename={my-product-cr-name}.yaml --namespace={my-product-namespace} +---- diff --git a/modules/configuring/snip-provisioning-your-custom-configuration-appconfig-step-with-optional-steps-disabled.adoc b/modules/configuring/snip-provisioning-your-custom-configuration-appconfig-step-with-optional-steps-disabled.adoc new file mode 100644 index 0000000000..5f68885c65 --- /dev/null +++ b/modules/configuring/snip-provisioning-your-custom-configuration-appconfig-step-with-optional-steps-disabled.adoc @@ -0,0 +1,70 @@ +.. For a production environment, start with the following setup: ++ +.`{my-app-config-file}` +[source,yaml,subs="+attributes,+quotes"] +---- +app: + title: _<{product}>_ + branding: + fullLogo: ${BASE64_EMBEDDED_FULL_LOGO} + fullLogoWidth: 110px + iconLogo: ${BASE64_EMBEDDED_ICON_LOGO} +backend: + cache: + store: redis + connection: ${REDIS_CONNECTION} +techdocs: + cache: + ttl: 3600000 +catalog: + providers: + github: + providerId: + organization: "${GITHUB_INTEGRATION_ORGANIZATION}" + schedule: + frequency: + minutes: 30 + initialDelay: + seconds: 15 + timeout: + minutes: 15 +integrations: + github: + - host: ${GITHUB_INTEGRATION_HOST_DOMAIN} + apps: + - appId: ${GITHUB_INTEGRATION_APP_ID} + clientId: ${GITHUB_INTEGRATION_CLIENT_ID} + clientSecret: ${GITHUB_INTEGRATION_CLIENT_SECRET} + privateKey: | + ${GITHUB_INTEGRATION_PRIVATE_KEY_FILE} +permission: + enabled: true + rbac: + admin: + users: + - name: user:default/ + pluginsWithPermission: + - catalog + - scaffolder + - permission +---- +Most fields use environment variables that you defined in secrets in the previous step. +`app`:: +`title`::: Enter your Developer Hub instance display name, such as _<{product}>_. +`branding`::: Set your custom logo. ++ +Optionally, customize the width of the branding logo by changing value for the `fullLogoWidth` field. The following units are supported: integer, px, em, rem, percentage. +`backend`:: +`cache`::: Enable the plugins assets cache. +`techdocs`:: +`cache`::: Enable the Techdocs cache. +`catalog`:: +`provider`::: +`github`:::: Enable GitHub repository discovery. +`integrations`:: +`github`::: Enable GitHub repository discovery. +[id='enabling-and-giving-access-to-rbac'] +`permissions`:: Enable Role-based access control. +Enter your policy administrator name. + +.. Additionally, link:{authentication-book-url}[provision users and enable authentication with your external identity provider]. diff --git a/modules/configuring/snip-provisioning-your-custom-configuration-appconfig-step-with-optional-steps-enabled.adoc b/modules/configuring/snip-provisioning-your-custom-configuration-appconfig-step-with-optional-steps-enabled.adoc new file mode 100644 index 0000000000..1b831511ae --- /dev/null +++ b/modules/configuring/snip-provisioning-your-custom-configuration-appconfig-step-with-optional-steps-enabled.adoc @@ -0,0 +1,35 @@ + +** To prepare a deployment with the {product} Operator on {ocp-short}, you can start with an empty file. + +** To prepare a deployment with the {product} Helm chart, or on Kubernetes, enter the {product-short} base URL in the relevant fields in your `{my-app-config-file}` file to ensure proper functionality of {product-short}. +The base URL is what a {product-short} user sees in their browser when accessing {product-short}. +The relevant fields are `baseUrl` in the `app` and `backend` sections, and `origin` in the `backend.cors` subsection: ++ +.Configuring the `baseUrl` in `{my-app-config-file}` +==== +[source,yaml,subs="+attributes,+quotes"] +---- +app: + title: {product} + baseUrl: {my-product-url} + +backend: + auth: + externalAccess: + - type: legacy + options: + subject: legacy-default-config + secret: "${BACKEND_SECRET}" + baseUrl: {my-product-url} + cors: + origin: {my-product-url} +---- +==== + +** Optionally, enter your configuration such as: + +*** {authentication-book-link}[{authentication-book-title}]. +*** {authorization-book-link}[{authorization-book-title}]. +*** {customizing-book-link}[Customization]. +*** {customizing-book-link}#configuring-an-rhdh-instance-with-tls-in-kubernetes_{context}[Configure your {ocp-short} integration]. + diff --git a/modules/configuring/snip-provisioning-your-custom-configuration-next-steps-in-default-context.adoc b/modules/configuring/snip-provisioning-your-custom-configuration-next-steps-in-default-context.adoc new file mode 100644 index 0000000000..70d2ef9f02 --- /dev/null +++ b/modules/configuring/snip-provisioning-your-custom-configuration-next-steps-in-default-context.adoc @@ -0,0 +1,4 @@ +.Next steps +* {configuring-book-link}#configuring-external-postgresql-databases[Provision your PostgreSQL database secrets] +* {installing-and-viewing-plugins-book-link}[Provision your dynamic plugins config map] +* {authorization-book-link}#managing-authorizations-by-using-external-files[Provision your RBAC policies config map] diff --git a/modules/configuring/snip-provisioning-your-custom-configuration-next-steps-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc b/modules/configuring/snip-provisioning-your-custom-configuration-next-steps-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc new file mode 100644 index 0000000000..89784779c0 --- /dev/null +++ b/modules/configuring/snip-provisioning-your-custom-configuration-next-steps-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc @@ -0,0 +1,6 @@ +.. Provision your PosgreSQL TLS certificates to the `{my-product-database-secrets}` secret in the _<{my-product-namespace}>_ project. ++ +[source,terminal,subs="+attributes,+quotes"] +---- +$ oc create secret generic {my-product-secrets} --from-file=postgres-ca.pem --from-file=postgres-crt.pem --from-file=postgres-key.key --namespace={my-product-namespace} +---- diff --git a/modules/configuring/snip-provisioning-your-custom-configuration-prerequisites-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc b/modules/configuring/snip-provisioning-your-custom-configuration-prerequisites-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc new file mode 100644 index 0000000000..648517c8fd --- /dev/null +++ b/modules/configuring/snip-provisioning-your-custom-configuration-prerequisites-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc @@ -0,0 +1,25 @@ +* You have the connection string to an active Redis server, such as `rediss://user:pass@cache.example.com:6379`. +For security, consider using a `rediss` secure server connection. +See xref:preparing-your-external-services[]. +* You have an external PostgreSQL database, with the following details. +See See xref:preparing-your-external-services[]. + +postgres-host::: Your PostgreSQL instance Domain Name System (DNS) or IP address. +postgres-port::: Your PostgreSQL instance port number, such as 5432. +postres-username::: The user name to connect to your PostgreSQL instance. +postgres-password::: The password to connect to your PostgreSQL instance. +postgres-ca.pem::: +postgres-key.key::: +postgres-crt.pem::: +TLS certificates to secure the connection to the database. + +* You have a GitHub App enabling access to the GitHub API for repository discovery, with the following details. +See xref:preparing-your-external-services[]. +GITHUB_INTEGRATION_APP_ID::: +Your GitHub integration App ID. +GITHUB_INTEGRATION_CLIENT_ID::: +Your GitHub integration App client ID. +GITHUB_INTEGRATION_CLIENT_SECRET::: +Your GitHub integration App client secret. +GITHUB_INTEGRATION_PRIVATE_KEY_FILE::: +Your GitHub integration App private key. diff --git a/modules/configuring/snip-provisioning-your-custom-configuration-secrets-steps-in-default-context.adoc b/modules/configuring/snip-provisioning-your-custom-configuration-secrets-steps-in-default-context.adoc new file mode 100644 index 0000000000..e7b8175dbb --- /dev/null +++ b/modules/configuring/snip-provisioning-your-custom-configuration-secrets-steps-in-default-context.adoc @@ -0,0 +1 @@ +* {authentication-book-link}[Enter your authentication secrets]. diff --git a/modules/configuring/snip-provisioning-your-custom-configuration-secrets-steps-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc b/modules/configuring/snip-provisioning-your-custom-configuration-secrets-steps-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc new file mode 100644 index 0000000000..3e9835fd3d --- /dev/null +++ b/modules/configuring/snip-provisioning-your-custom-configuration-secrets-steps-in-setting-up-and-configuring-your-first-rhdh-instance-context.adoc @@ -0,0 +1,53 @@ +.. Enter your custom logo. ++ +[source,subs="+attributes,+quotes"] +---- +BASE64_EMBEDDED_FULL_LOGO="data:image/svg+xml;base64," +BASE64_EMBEDDED_ICON_LOGO="data:image/svg+xml;base64," +---- + +`BASE64_EMBEDDED_FULL_LOGO`:: +Enter your logo for the expanded (pinned) sidebar as a base64 encoded SVG image. ++ +To encode your logo in base64, run: ++ +[source] +---- +$ base64 -i logo.svg +---- + +`BASE64_EMBEDDED_ICON_LOGO`:: +Enter your logo for the collapsed (unpinned) sidebar as a base64 encoded SVG image. + +.. Enter the connection string to your Redis server that caches plugin assets. ++ +[source] +---- +REDIS_CONNECTION=rediss://user:pass@cache.example.com:6379 +---- + +.. Enter your GitHub integration credentials: ++ +[source,subs="+quotes"] +---- +GITHUB_INTEGRATION_APP_ID=___ +GITHUB_INTEGRATION_CLIENT_ID=__ +GITHUB_INTEGRATION_CLIENT_SECRET=__ +GITHUB_INTEGRATION_HOST_DOMAIN=github.com +GITHUB_INTEGRATION_ORGANIZATION=__ +GITHUB_INTEGRATION_PRIVATE_KEY_FILE= __ +---- + +.. Enter your PosgreSQL database secrets: ++ +[source,subs="+quotes"] +---- +POSTGRES_PASSWORD: +POSTGRES_PORT: "" +POSTGRES_USER: +POSTGRES_HOST: +PGSSLMODE: verify-full +NODE_EXTRA_CA_CERTS: /opt/app-root/src/postgres-crt.pem +---- + +.. {authentication-book-link}[Enter your authentication secrets]. diff --git a/modules/installation/proc-install-operator.adoc b/modules/installation/proc-install-operator.adoc index 2ab577ade7..0af215e6fe 100644 --- a/modules/installation/proc-install-operator.adoc +++ b/modules/installation/proc-install-operator.adoc @@ -82,8 +82,3 @@ If you selected an *Automatic* approval strategy, the upgrade status should reso *** From the list of installed Operators, locate the {product} Operator name and details. *** Click *{product} Operator* to open the *Operator details* page for the {product} Operator. -[role="_additional-resources"] -.Additional resources - -* xref:proc-install-rhdh-ocp-operator_{context}[Deploying {product} on {ocp-short} with the Operator] -* {ocp-docs-link}/html-single/operators/index#olm-installing-from-operatorhub-using-web-console_olm-adding-operators-to-a-cluster[Installing from OperatorHub by using the web console] diff --git a/titles/configuring/master.adoc b/titles/configuring/master.adoc index bdf974fbb2..e3dc73ec39 100644 --- a/titles/configuring/master.adoc +++ b/titles/configuring/master.adoc @@ -11,12 +11,12 @@ include::artifacts/attributes.adoc[] :platform-cli-name: {ocp-brand-name} CLI ('oc') :a-platform-generic: an OpenShift :platform-generic: OpenShift +:optional-steps: enable [id="{context}"] = {title} {abstract} - include::assemblies/assembly-provisioning-a-custom-configuration.adoc[leveloffset=+1] @@ -35,7 +35,7 @@ include::assemblies/assembly-configuring-high-availability.adoc[leveloffset=+1] include::assemblies/assembly-configuring-a-proxy.adoc[leveloffset=+1] -include::modules/installation/proc-configuring-an-rhdh-instance-with-tls-in-kubernetes.adoc[leveloffset=+1] +include::modules/configuring/proc-configuring-an-rhdh-instance-with-tls-in-kubernetes.adoc[leveloffset=+1] include::assemblies/dynamic-plugins/assembly-using-the-dynamic-plugins-cache.adoc[leveloffset=+1] diff --git a/titles/install-rhdh-air-gapped/master.adoc b/titles/install-rhdh-air-gapped/master.adoc index 104ac69897..0b0ed23cc0 100644 --- a/titles/install-rhdh-air-gapped/master.adoc +++ b/titles/install-rhdh-air-gapped/master.adoc @@ -6,6 +6,7 @@ include::artifacts/attributes.adoc[] = {title} :context: title-install-rhdh-air-grapped :imagesdir: images +:optional-steps: disable include::modules/installation/con-airgapped-environment.adoc[leveloffset=+1] diff --git a/titles/install-rhdh-aks/master.adoc b/titles/install-rhdh-aks/master.adoc index 0e164dbf98..b631552ff2 100644 --- a/titles/install-rhdh-aks/master.adoc +++ b/titles/install-rhdh-aks/master.adoc @@ -14,6 +14,7 @@ include::artifacts/attributes.adoc[] [id="{context}"] = {title} :imagesdir: images +:optional-steps: disable {abstract} diff --git a/titles/install-rhdh-eks/master.adoc b/titles/install-rhdh-eks/master.adoc index b3eeb48d78..48b9e529be 100644 --- a/titles/install-rhdh-eks/master.adoc +++ b/titles/install-rhdh-eks/master.adoc @@ -14,6 +14,7 @@ include::artifacts/attributes.adoc[] [id="{context}"] = {title} :imagesdir: images +:optional-steps: disable {abstract} diff --git a/titles/install-rhdh-gke/master.adoc b/titles/install-rhdh-gke/master.adoc index e6c4652d04..e6153e64ba 100644 --- a/titles/install-rhdh-gke/master.adoc +++ b/titles/install-rhdh-gke/master.adoc @@ -14,6 +14,7 @@ include::artifacts/attributes.adoc[] [id="{context}"] = {title} :imagesdir: images +:optional-steps: disable {abstract} diff --git a/titles/setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc b/titles/setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc index 0a74e7ec89..f2f3875d2e 100644 --- a/titles/setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc +++ b/titles/setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc @@ -9,4 +9,25 @@ include::artifacts/attributes.adoc[] {abstract} +include::modules/configuring/con-checklist-to-run-your-first-rhdh-instance-in-production.adoc[leveloffset=+1] + +include::modules/installation/proc-install-operator.adoc[leveloffset=+1] + +include::modules/configuring/proc-preparing-your-external-services.adoc[leveloffset=+1] + +include::modules/configuring/proc-provisioning-your-custom-configuration.adoc[leveloffset=+1] + +// TODO: Understanding the software catalog + include::assemblies/assembly-enabling-authentication-with-mandatory-steps-only.adoc[leveloffset=+1] + + +include::modules/configuring/proc-using-the-operator-to-run-rhdh-with-your-custom-configuration-in-getting-started-context.adoc[leveloffset=+1] + +include::modules/customizing-the-appearance/proc-customize-rhdh-theme-mode.adoc[leveloffset=+1] + +include::assemblies/assembly-managing-authorizations-by-using-the-rhdh-web-ui.adoc[leveloffset=+1] + +// TODO: Importing manually a Git repository +// TODO: Running a CI pipeline: GitHub; CF + Tekton +// TODO: Configuring Software Templates From 9d04390542de65803861c0c99c3c4bbb8d9aec6c Mon Sep 17 00:00:00 2001 From: Nick Boldt Date: Wed, 8 Oct 2025 17:33:41 -0300 Subject: [PATCH 13/22] chore: version bump for 1.8 in main branch (#1433) Signed-off-by: Nick Boldt --- artifacts/attributes.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/artifacts/attributes.adoc b/artifacts/attributes.adoc index 951a196846..287d315f6f 100644 --- a/artifacts/attributes.adoc +++ b/artifacts/attributes.adoc @@ -12,11 +12,11 @@ :product-very-short: RHDH :product-local: Red Hat Developer Hub Local :product-local-very-short: RHDH Local -:product-version: 1.7 -:product-bundle-version: 1.7.0 -:product-chart-version: 1.7.0 -:product-backstage-version: 1.39.1 -:product-version-next: 1.7 +:product-version: 1.8 +:product-bundle-version: 1.8.0 +:product-chart-version: 1.8.0 +:product-backstage-version: 1.42.5 +:product-version-next: 1.9 :product-custom-resource-type: Backstage :product-docs-url: https://docs.redhat.com/en/documentation/red_hat_developer_hub :product-docs-link: link:{product-docs-url}/{product-version} From 21029d297d8893c0bb38968b301268ffbec95add Mon Sep 17 00:00:00 2001 From: Nick Boldt Date: Wed, 8 Oct 2025 17:39:35 -0300 Subject: [PATCH 14/22] chore: update min/max OCP versions supported (4.16-4.19) (#1432) * chore: update min/max OCP versions supported (4.16-4.19) Signed-off-by: Nick Boldt * Update artifacts/attributes.adoc - set minimum k8s version to match that of OCP 4.16 (1.29) --------- Signed-off-by: Nick Boldt --- artifacts/attributes.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artifacts/attributes.adoc b/artifacts/attributes.adoc index 287d315f6f..0af7875790 100644 --- a/artifacts/attributes.adoc +++ b/artifacts/attributes.adoc @@ -24,9 +24,9 @@ // Minimum and current latest supported versions :ansible-automation-platform-version: 2.4 :keycloak-version: 26.0 -:kubernetes-version: 1.24 -:ocp-version-min: 4.14 -:ocp-version: 4.18 +:kubernetes-version: 1.29 +:ocp-version-min: 4.16 +:ocp-version: 4.19 :osd-version: 4 :rhoserverless-version: 1.36 From 82f64da9fa882294f8c5bbc3655efd93ca03abc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20Flore-Th=C3=A9bault?= Date: Thu, 9 Oct 2025 14:01:26 +0200 Subject: [PATCH 15/22] RHDHBUGS-2070 - Added oc-mirror prerequisite (#1430) Co-authored-by: Armel Soro Co-authored-by: Judith Magak <124673476+jmagak@users.noreply.github.com> --- ...stall-rhdh-airgapped-partial-k8s-helm.adoc | 8 +-- ...proc-install-rhdh-helm-airgapped-full.adoc | 59 +++++++++---------- ...c-install-rhdh-helm-airgapped-partial.adoc | 17 +++--- ...-install-rhdh-operator-airgapped-full.adoc | 31 +++++----- ...stall-rhdh-operator-airgapped-partial.adoc | 40 ++++++++----- 5 files changed, 82 insertions(+), 73 deletions(-) diff --git a/modules/installation/proc-install-rhdh-airgapped-partial-k8s-helm.adoc b/modules/installation/proc-install-rhdh-airgapped-partial-k8s-helm.adoc index 1027b0da6f..8aab402d21 100644 --- a/modules/installation/proc-install-rhdh-airgapped-partial-k8s-helm.adoc +++ b/modules/installation/proc-install-rhdh-airgapped-partial-k8s-helm.adoc @@ -19,7 +19,7 @@ In a partially disconnected environment, the cluster cannot access external regi . In a terminal, download and extract the Helm chart by running the following commands: + -[source,terminal,subs="attributes+"] +[source,terminal,subs="+attributes,+quotes"] ---- helm repo add __ https://charts.openshift.io/ helm repo update @@ -27,10 +27,10 @@ helm pull __/redhat-developer-hub --version __/redhat-developer-hub --version __ > values.default.yaml ---- + -where +where: -__ :: Specifies the name of the Helm chart repository, for example, `openshift-helm-charts`. -__ :: Specifies the {product} version that you want to use, for example, `{product-chart-version}`. +__ :: Enter the name of the Helm chart repository, for example, `openshift-helm-charts`. +__ :: Enter the {product} version that you want to use, for example, `{product-chart-version}`. + . Use `yq` to extract the image digests by running the following commands: + diff --git a/modules/installation/proc-install-rhdh-helm-airgapped-full.adoc b/modules/installation/proc-install-rhdh-helm-airgapped-full.adoc index df5c7d79ef..b7fdf1c048 100644 --- a/modules/installation/proc-install-rhdh-helm-airgapped-full.adoc +++ b/modules/installation/proc-install-rhdh-helm-airgapped-full.adoc @@ -8,38 +8,36 @@ If your network has access to the registry through a bastion host, you can use t .Prerequisites * You have set up your workstation. -** You have access to the registry.redhat.io. +** You have an account in https://developers.redhat.com/[{rhdeveloper-name}] portal. ** You have access to the charts.openshift.io Helm chart repository. ** You have installed the {openshift-cli} on your workstation. -* {ocp-docs-link}/html-single/disconnected_environments/index#about-installing-oc-mirror-v2[You have installed the oc-mirror OpenShift CLI plugin]. -** You have an account in https://developers.redhat.com/[{rhdeveloper-name}] portal. -** You have set up your intermediary host. -** Your host has access to the disconnected cluster and to the target mirror registry, for example, the {ocp-brand-name} image registry. For more information about exposing the {ocp-short} image registry, see {ocp-docs-link}/html-single/registry/index#securing-exposing-registry[Exposing the registry]. -** {ocp-docs-link}/html-single/disconnected_environments/index#about-installing-oc-mirror-v2[You have installed the oc-mirror OpenShift CLI plugin]. +** You have installed the {ocp-docs-link}/html-single/disconnected_environments/index#installation-oc-mirror-installing-plugin_about-installing-oc-mirror-v2[`oc-mirror`] tool, with a version corresponding to the version of your {ocp-short} cluster. +* You have set up your intermediary host. +** Your host has access to the link:https://registry.redhat.io[{company-name} Ecosystem Catalog]. +** Your host has access to image registry on the destination host. +See {ocp-docs-link}/html-single/registry/index#securing-exposing-registry[Exposing the registry]. +* You have set up your destination host. ** You have installed {ocp-brand-name} {ocp-version-min} or later. -** You have installed the {openshift-cli} on your workstation. -* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +** Your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure . Create an `ImageSetConfiguration` file to specify the resources that you want to mirror. For example: + -[source,terminal,subs="+quotes"] +[source,yaml,subs="+attributes,+quotes"] ---- apiVersion: mirror.openshift.io/v1alpha2 kind: ImageSetConfiguration mirror: helm: repositories: - - name: __ (1) - url: __ (2) + - name: openshift-charts + url: https://charts.openshift.io charts: - - name: __ (3) - version: "__" (4) + - name: redhat-developer-hub + version: "{product-version}" ---- -<1> The name of the repository that you want to mirror, for example, `openshift-charts`. -<2> The URL for the repository that you want to mirror, for example, `https://charts.openshift.io`. -<3> The name of the Helm chart that you want to mirror, for example, `redhat-developer-hub`. -<4> The version of {product} that you want to use, for example, `{product-version}` +where: +`version: "{product-version}"`:: Enter the {product} version to mirror. . Mirror the resources specified in the `ImageSetConfiguration.yaml` file by running the `oc-mirror` command. For example: + @@ -51,11 +49,9 @@ oc-mirror --config=__/ImageSetConfiguration.yaml _` :: Specifies the location of your image set configuration file on your system, for example, `.user`. +`` :: Enter the location of your image set configuration file on your system, for example, `.user`. -`` :: Specifies the name of your mirror configuration yaml file, for example, `mirror-config.yaml` - -`` :: Specifies the location of your directory where the mirror archive will be created, for example, `pass:[file://.user]`. +`` :: Enter the location of your directory where the mirror archive will be created, for example, `pass:[file://.user]`. -- + [NOTE] @@ -63,7 +59,8 @@ where: Running the `oc-mirror` command generates a local workspace containing the mirror archive file, the Helm chart, and a `ImageContentSourcePolicy` (ICSP) manifest. The ICSP manifest contains an `imageContentSourcePolicy.yaml` file that you must apply against the cluster in a later step. ==== + -.Example output +Example output: ++ [source,terminal,subs="+quotes"] ---- Creating archive /path/to/mirror-archive/mirror_seq1_000000.tar @@ -85,9 +82,9 @@ oc-mirror --from __ __ -- where: -`` :: Specifies the name of the file containing the resources that you want to mirror, for example,`mirror_seq1_0000.tar`. +`` :: Enter the name of the file containing the resources that you want to mirror, for example,`mirror_seq1_0000.tar`. -`` :: Specifies the name of the target registry that you want to push the mirrored images to, for example, `docker://registry.localhost:5000`. +`` :: Enter the name of the target registry that you want to push the mirrored images to, for example, `docker://registry.localhost:5000`. -- + .Example output @@ -123,9 +120,9 @@ oc apply -f __/__/ImageContentSourcePoli -- where: -`` :: Specifies the name of your workspace directory, for example, `oc-mirror-workspace`. +`` :: Enter the name of your workspace directory, for example, `oc-mirror-workspace`. -`` :: Specifies the name of your results directory, for example, `results-1738070846`. +`` :: Enter the name of your results directory, for example, `results-1738070846`. -- . In your air-gapped environment, deploy the Helm chart to the namespace that you want to use by running the `helm install` command with `namespace` and `set` options. For example: + @@ -140,13 +137,13 @@ helm install __ __/__/cha -- where: -`` :: Specifies the name of your {product} instance, for example, `my-rhdh`. +`` :: Enter the name of your {product} instance, for example, `{my-product-namespace}`. -`` :: Specifies the name of your workspace directory, for example, `oc-mirror-workspace`. +`` :: Enter the name of your workspace directory, for example, `oc-mirror-workspace`. -`` :: Specifies the name of your results directory, for example, `results-1738070846`. +`` :: Enter the name of your results directory, for example, `results-1738070846`. -`` :: Specifies the name of the archive file containing the resources that you want to mirror, for example, `redhat-developer-hub-1.4.1.tgz`. +`` :: Enter the name of the archive file containing the resources that you want to mirror, for example, `redhat-developer-hub-1.4.1.tgz`. -`` :: Specifies the namespace that you want to deploy the Helm chart to, for example, `{my-product-namespace}`. +`` :: Enter the namespace that you want to deploy the Helm chart to, for example, `{my-product-namespace}`. -- diff --git a/modules/installation/proc-install-rhdh-helm-airgapped-partial.adoc b/modules/installation/proc-install-rhdh-helm-airgapped-partial.adoc index f3771e4e9d..d4516ef8dd 100644 --- a/modules/installation/proc-install-rhdh-helm-airgapped-partial.adoc +++ b/modules/installation/proc-install-rhdh-helm-airgapped-partial.adoc @@ -13,7 +13,7 @@ If your network has access to the `registry.redhat.io` registry and the `charts. * You have access to a mirror registry that can be reached from the disconnected cluster, for example, the {ocp-short} image registry. For more information about exposing the {ocp-short} image registry, see {ocp-docs-link}/html-single/registry/index#securing-exposing-registry[Exposing the registry]. * You are logged in to your target mirror registry and have permissions to push images to it. For more information, see {ocp-docs-link}/html-single/disconnected_environments/index#installation-adding-registry-pull-secret_about-installing-oc-mirror-v2[Configuring credentials that allow images to be mirrored]. * You have installed the {openshift-cli} on your workstation. -* {ocp-docs-link}/html-single/disconnected_environments/index#about-installing-oc-mirror-v2[You have installed the oc-mirror OpenShift CLI plugin]. +* Recommended on {ocp-short}: You have installed the {ocp-docs-link}/html-single/disconnected_environments/index#installation-oc-mirror-installing-plugin_about-installing-oc-mirror-v2[`oc-mirror`] tool, with a version corresponding to the version of your {ocp-short} cluster. * You have an account in https://developers.redhat.com/[{rhdeveloper-name}] portal. * Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. @@ -29,23 +29,20 @@ oc login -u -p https://api.:6443 . Create an `ImageSetConfiguration.yaml` file. . In your `ImageSetConfiguration.yaml` file, specify the resources that you want to mirror. For example: + -[source,terminal,subs="+quotes"] +[source,yaml,subs="+attributes,+quotes"] ---- apiVersion: mirror.openshift.io/v1alpha2 kind: ImageSetConfiguration mirror: helm: repositories: - - name: __ (1) - url: __ (2) + - name: openshift-charts + url: https://charts.openshift.io charts: - - name: __ (3) - version: "__" (4) + - name: redhat-developer-hub + version: "{product-version}" ---- -<1> The name of the repository containing the Helm chart that you want to mirror, for example, `openshift-charts`. -<2> The URL for the repository containing the Helm chart that you want to mirror, for example, `https://charts.openshift.io`. -<3> The name of the Helm chart containing the images that you want to mirror, for example, `redhat-developer-hub`. -<4> The {product} version that you want to use, for example, `{product-version}` +`version: "{product-version}"`:: Enter the {product} version to mirror. . Mirror the resources specified in the image set configuration file directly to the target registry by running the `oc-mirror` command. For example: + diff --git a/modules/installation/proc-install-rhdh-operator-airgapped-full.adoc b/modules/installation/proc-install-rhdh-operator-airgapped-full.adoc index f266dfa906..ba8c2fb17e 100644 --- a/modules/installation/proc-install-rhdh-operator-airgapped-full.adoc +++ b/modules/installation/proc-install-rhdh-operator-airgapped-full.adoc @@ -16,27 +16,29 @@ If your network has access to the registry through a bastion host, you can use t * You have installed `umoci` CLI tool. * You have an active `oc registry`, `podman`, or `skopeo` session to the `registry.redhat.io` {company-name} Ecosystem Catalog. For more information, see link:link:https://access.redhat.com/articles/RegistryAuthentication[{company-name} Container Registry Authentication]. * You have installed the `opm` CLI tool. For more information, see {ocp-docs-link}/html/cli_tools/opm-cli#olm-about-opm_cli-opm-install[Installing the opm CLI]. +* Recommended on {ocp-short}: You have installed the {ocp-docs-link}/html-single/disconnected_environments/index#installation-oc-mirror-installing-plugin_about-installing-oc-mirror-v2[`oc-mirror`] tool, with a version corresponding to the version of your {ocp-short} cluster. * Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure . Download the mirroring script to disk by running the following command: + -[source,terminal,subs="attributes+"] +[source,terminal,subs="+attributes,+quotes"] ---- curl -sSLO https://raw.githubusercontent.com/redhat-developer/rhdh-operator/refs/heads/release-{product-version}/.rhdh/scripts/prepare-restricted-environment.sh ---- + . Run the mirroring script by using the `bash` command with the appropriate set of options: + -[source,terminal,subs="attributes+"] +[source,terminal,subs="+attributes,+quotes"] ---- bash prepare-restricted-environment.sh --filter-versions "{product-version}" - --to-dir __ <1> - [--use-oc-mirror true] <2> + --to-dir __ + [--use-oc-mirror true] ---- -<1> Specifies the absolute path to a directory where you want to pull all of the necessary images with the `--to-dir` option, for example, `/home/user/rhdh-operator-mirror-dir`. -<2> (Optional) Uses the `oc-mirror` {ocp-short} CLI plugin to mirror images. +where: +`--to-dir __`:: Enter the absolute path to a directory where you want to pull all of the necessary images, for example, `/home/user/rhdh-operator-mirror-dir`. +`--use-oc-mirror true`:: (Recommended on {ocp-short}) Use the `oc-mirror` {ocp-short} CLI plugin to mirror images. + [NOTE] ==== @@ -48,15 +50,16 @@ The script can take several minutes to complete as it copies multiple images to + [source,terminal,subs="+quotes,+attributes"] ---- -bash __/install.sh <1> - --from-dir __ <2> - [--to-registry __] <3> - [--use-oc-mirror true] <4> +bash __/install.sh + --from-dir __ + [--to-registry __] + [--use-oc-mirror true] ---- -<1> The downloaded image and the absolute path to the directory where it is stored on your system. -<2> Specifies the directory where you want to pull all of the necessary images with the `--to-dir` option. -<3> Specifies the URL for the target mirror registry where you want to mirror the images. -<4> (Optional) Uses the `oc-mirror` {ocp-short} CLI plugin to mirror images. +where: +`__/install.sh`:: Enter the downloaded installation script and the absolute path to the directory where it is stored on your system. +`--from-dir __`:: Enter the directory where you want to pull all of the necessary images. +`--to-registry`:: (Optional) Enter the URL for the target mirror registry where you want to mirror the images. +`--use-oc-mirror true`:: Recommended on {ocp-short}: Use the `oc-mirror` {ocp-short} CLI plugin to mirror images. + [IMPORTANT] ==== diff --git a/modules/installation/proc-install-rhdh-operator-airgapped-partial.adoc b/modules/installation/proc-install-rhdh-operator-airgapped-partial.adoc index 74f1e98203..49474a7b9f 100644 --- a/modules/installation/proc-install-rhdh-operator-airgapped-partial.adoc +++ b/modules/installation/proc-install-rhdh-operator-airgapped-partial.adoc @@ -3,32 +3,42 @@ [id="proc-install-rhdh-operator-airgapped-partial.adoc_{context}"] = Installing {product} in a partially disconnected environment with the Operator -On an {ocp-short} cluster operating on a restricted network, public resources are not available. However, deploying the {product} Operator and running {product-short} requires the following public resources: +On an {ocp-short} cluster operating on a restricted network, public resources are not available. +However, deploying the {product} Operator and running {product-short} requires the following public resources: * Operator images (bundle, operator, catalog) * Operands images ({product-very-short}, PostgreSQL) To make these resources available, replace them with their equivalent resources in a mirror registry accessible to your cluster. -You can use a helper script that mirrors the necessary images and provides the necessary configuration to ensure those images are used when installing the {product} Operator and creating {product-short} instances. This script requires a target mirror registry. You likely have a target mirror registry if your cluster is already operating on a disconnected network. If you do not already have a target registry, and if you have an {ocp-short} cluster, you might want to expose and leverage the internal cluster registry. +You can use a helper script that mirrors the necessary images and provides the necessary configuration to ensure those images are used when installing the {product} Operator and creating {product-short} instances. +This script requires a target mirror registry. +You likely have a target mirror registry if your cluster is already operating on a disconnected network. +If you do not already have a target registry, and if you have an {ocp-short} cluster, you might want to expose and leverage the internal cluster registry. -When connected to a {ocp-short} cluster, the helper script detects it and automatically exposes the cluster registry. If connected to a Kubernetes cluster, you can manually specify the target registry to mirror the images. +When connected to a {ocp-short} cluster, the helper script detects it and automatically exposes the cluster registry. +If connected to a Kubernetes cluster, you can manually specify the target registry to mirror the images. .Prerequisites -* You have installed Podman 5.3 or later. For more information, see link:https://podman.io/docs/installation[Podman Installation Instructions]. +* You have installed Podman 5.3 or later. +For more information, see link:https://podman.io/docs/installation[Podman Installation Instructions]. * You have installed Skopeo 1.17 or later. * You have installed `yq` 4.44 or later. * You have installed the GNU `sed` command line text editor. * You have installed `umoci` CLI tool. -* You have an active `oc registry`, `podman`, or `skopeo` session to the `registry.redhat.io` {company-name} Ecosystem Catalog. For more information, see link:link:https://access.redhat.com/articles/RegistryAuthentication[{company-name} Container Registry Authentication]. -* You have an active `skopeo` session with administrative access to the target mirror registry. For more information, see link:https://github.com/containers/skopeo#authenticating-to-a-registry[Authenticating to a registry]. -* You have installed the `opm` CLI tool. For more information, see {ocp-docs-link}/html/cli_tools/opm-cli#olm-about-opm_cli-opm-install[Installing the opm CLI]. +* You have an active `oc registry`, `podman`, or `Skopeo` session to the `registry.redhat.io` {company-name} Ecosystem Catalog. +For more information, see link:link:https://access.redhat.com/articles/RegistryAuthentication[{company-name} Container Registry Authentication]. +* You have an active `Skopeo` session with administrative access to the target mirror registry. +For more information, see link:https://github.com/containers/skopeo#authenticating-to-a-registry[Authenticating to a registry]. +* You have installed the `opm` CLI tool. +For more information, see {ocp-docs-link}/html/cli_tools/opm-cli#olm-about-opm_cli-opm-install[Installing the opm CLI]. * If you are using an {ocp-short} cluster, you have the following prerequisites: -** (Optional) You have installed the `oc-mirror` {ocp-short} CLI plugin if you want to use it to mirror images. +** Recommended: You have installed the `oc-mirror` tool, with a version corresponding to the version of your {ocp-short} cluster. * If you are using a supported Kubernetes cluster, you have the following prerequisites: ** You have installed the Operator Lifecycle Manager (OLM) on the disconnected cluster. ** You have a mirror registry that is reachable from the disconnected cluster. -* Make sure that your system meets the minimum sizing requirements. See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. +* Make sure that your system meets the minimum sizing requirements. +See {about-book-link}#rhdh-sizing_about-rhdh[Sizing requirements for {product}]. .Procedure . In your terminal, navigate to the directory where you want to save the mirroring script. @@ -45,11 +55,12 @@ curl -sSLO https://raw.githubusercontent.com/redhat-developer/rhdh-operator/refs ---- bash prepare-restricted-environment.sh \ --filter-versions "{product-version}" \ - [--to-registry __] \ <1> - [--use-oc-mirror true] <2> + [--to-registry __] \ + [--use-oc-mirror true] ---- -<1> Specifies the URL for the target mirror registry where you want to mirror the images. -<2> (Optional) Uses the `oc-mirror` {ocp-short} CLI plugin to mirror images. +where: +`--to-registry _`:: Enter the URL for the target mirror registry where you want to mirror the images. +`--use-oc-mirror true`:: Optional: Use the `oc-mirror` {ocp-short} CLI plugin to mirror images. + [NOTE] ==== @@ -66,7 +77,8 @@ kubectl -n rhdh-operator get pods ---- .Next steps -* Use the Operator to create a {product} instance on a supported platform. For more information, see the following documentation for the platform that you want to use: +* Use the Operator to create a {product} instance on a supported platform. +For more information, see the following documentation for the platform that you want to use: ** {installing-on-ocp-book-link}#assembly-install-rhdh-ocp-operator[Installing {product} on {ocp-short} with the Operator] ** {installing-on-eks-book-link}#assembly-install-rhdh-eks-operator[Installing {product-short} on {eks-short} with the Operator] ** {installing-on-aks-book-link}#proc-rhdh-deploy-aks-operator_title-install-rhdh-aks[Installing {product-short} on {aks-short} with the Operator] From a9830e16f297c670fb531655611b58e5d0a6e1ea Mon Sep 17 00:00:00 2001 From: Priyanka Abel Date: Thu, 9 Oct 2025 18:36:09 +0530 Subject: [PATCH 16/22] RHIDP-8630: Added notification during software template version update (#1431) * Added notification during software template version update * Incorporated Diana's comments * Incorporated Judy's comments --- .../assembly-configuring-templates.adoc | 1 + ...template-version-update-notifications.adoc | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 modules/customizing-templates/proc-enabling-software-template-version-update-notifications.adoc diff --git a/assemblies/assembly-configuring-templates.adoc b/assemblies/assembly-configuring-templates.adoc index 41143c84bc..6acafde1a2 100644 --- a/assemblies/assembly-configuring-templates.adoc +++ b/assemblies/assembly-configuring-templates.adoc @@ -19,6 +19,7 @@ include::modules/customizing-templates/proc-creating-a-new-software-component-us include::modules/customizing-templates/proc-searching-and-filtering-software-templates.adoc[leveloffset=+1] include::modules/customizing-templates/proc-adding-templates.adoc[leveloffset=+1] include::modules/customizing-templates/proc-versioning-software-templates.adoc[leveloffset=+1] +include::modules/customizing-templates/proc-enabling-software-template-version-update-notifications.adoc[leveloffset=+1] [role="_additional-resources"] .Additional resources diff --git a/modules/customizing-templates/proc-enabling-software-template-version-update-notifications.adoc b/modules/customizing-templates/proc-enabling-software-template-version-update-notifications.adoc new file mode 100644 index 0000000000..023e046e25 --- /dev/null +++ b/modules/customizing-templates/proc-enabling-software-template-version-update-notifications.adoc @@ -0,0 +1,73 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-enabling-software-template-version-update-notifications_{context}"] += Enabling Software Template version update notifications in {product} + +As a platform engineer, you can enable notification alerts for template version updates using the `@backstage-community/plugin-catalog-backend-module-scaffolder-relation-processor` module, an extension to the `catalog-backend` plugin. When enabled, this module automatically notifies component owners whenever the Software Template used to generate their components is updated to a new version. + +This functionality uses the `spec.scaffoldedFrom` field in catalog entities. This field links Software Templates to the entities they have scaffolded. By tracking this relationship, the module helps teams stay informed and take advantage of the latest improvements or fixes. + +The `plugin-catalog-backend-module-scaffolder-relation-processor` module is disabled by default. + + +.Prerequisites + +* You have installed and configured the Backstage backend notification plugin `@backstage/plugin-notifications-backend`. +* You have installed and configured the Backstage frontend plugin `@backstage/plugin-notifications`. + + +.Procedure + +. To enable the notifications, in your `{product} app-config.yaml` file, add the following codes: +.. In the `dynamicPlugins:frontend` section: ++ +[source,yaml] +---- +frontend: + backstage.plugin-notifications: + dynamicRoutes: + - importName: NotificationPage + menuItem: + config: + props: + titleCounterEnabled: true + webNotificationsEnabled: false + importName: NotificationsSidebarItem + path: /notifications +---- +.. In a new section: ++ +[source,yaml] +---- +scaffolder: + notifications: + templateUpdate: + enabled: true # Set to false to disable notifications +---- +You can also customize the notification title and description as shown in the following code: ++ +[source,yaml] +---- +scaffolder: + notifications: + templateUpdate: + enabled: true + message: + title: 'Custom title for $ENTITY_DISPLAY_NAME' + description: 'Custom description' +---- ++ +where: + +`enabled`:: Set to `true` to enable the notification. Default value is `false`. +`message:title`:: Enter the notification title. +`message:description`:: Enter the notification description. + +[NOTE] +==== +Both `message:title` and `message:description` support the template variable `$ENTITY_DISPLAY_NAME`. The system automatically substitutes this variable with the title (or the name, if the title is missing) of the entity scaffolded from the updated template. +==== + +.Verification +* In your {product} instance, on the left navigation menu, you are able to see `Notifications`, or, if configured, the custom title. +* When you update the version number in the Software Template, you receive a notification. \ No newline at end of file From cf02435e8b44279cad1df35be70e7f117e510e20 Mon Sep 17 00:00:00 2001 From: Priyanka Abel Date: Thu, 9 Oct 2025 18:38:08 +0530 Subject: [PATCH 17/22] RHIDP-9010: AEM-related changes (#1426) * AEM-related changes * Minor change Judy's comment * Incorporated Fabrice's comments * Incorporated Judy's comments * Incorporated Fabrice's final comments --- .../assembly-configuring-the-quickstarts.adoc | 2 + .../assembly-customizing-the-appearance.adoc | 6 +- ...ing-action-button-as-a-dynamic-plugin.adoc | 33 ++++---- ...ks-and-starred-items-in-global-header.adoc | 18 ++--- ...configuring-logo-in-the-global-header.adoc | 6 +- .../proc-customize-rhdh-global-header.adoc | 28 ++++--- ...eferred-username-in-profile-drop-down.adoc | 11 +-- .../proc-enabling-logo-in-the-sidebar.adoc | 10 +-- .../ref-mount-points.adoc | 22 ++++-- .../proc-customize-rhdh-quickstart.adoc | 39 +++------- .../proc-disabling-rhdh-quickstart.adoc | 20 +++++ ... => con-about-rhdh-sidebar-menuitems.adoc} | 4 +- ...c-configuring-dynamic-plugin-menuitem.adoc | 78 ++++++++++++------- .../proc-customize-rhdh-branding-logo.adoc | 13 +--- .../proc-customize-rhdh-page-theme.adoc | 25 +++--- .../proc-customize-rhdh-palette.adoc | 23 +++--- .../proc-customize-rhdh-theme-mode.adoc | 16 ++-- ...ing-custom-theme-using-dynamic-plugin.adoc | 6 +- ...ifying-or-adding-rhdh-custom-menuitem.adoc | 77 +++++++++--------- .../proc-customizing-the-home-page-cards.adoc | 6 ++ ...provide-data-to-the-quick-access-card.adoc | 6 +- ...ponents-manually-in-the-rhdh-instance.adoc | 1 - ...ng-components-in-the-software-catalog.adoc | 2 +- ...ng-components-in-the-software-catalog.adoc | 2 +- .../proc-viewing-software-catalog-yaml.adoc | 2 +- 25 files changed, 239 insertions(+), 217 deletions(-) create mode 100644 modules/configuring-the-quickstarts/proc-disabling-rhdh-quickstart.adoc rename modules/customizing-the-appearance/{con-customize-rhdh-sidebar-menuitems.adoc => con-about-rhdh-sidebar-menuitems.adoc} (81%) diff --git a/assemblies/assembly-configuring-the-quickstarts.adoc b/assemblies/assembly-configuring-the-quickstarts.adoc index 1d34a2d5a1..8d5801327a 100644 --- a/assemblies/assembly-configuring-the-quickstarts.adoc +++ b/assemblies/assembly-configuring-the-quickstarts.adoc @@ -8,4 +8,6 @@ include::modules/configuring-the-quickstarts/con-about-quickstarts.adoc[leveloff include::modules/configuring-the-quickstarts/proc-customize-rhdh-quickstart.adoc[leveloffset=+1] +include::modules/configuring-the-quickstarts/proc-disabling-rhdh-quickstart.adoc[leveloffset=+2] + include::modules/configuring-the-quickstarts/proc-starting-and-completing-modules-in-quickstarts.adoc[leveloffset=+1] diff --git a/assemblies/assembly-customizing-the-appearance.adoc b/assemblies/assembly-customizing-the-appearance.adoc index 8ef97012c7..b315a089d8 100644 --- a/assemblies/assembly-customizing-the-appearance.adoc +++ b/assemblies/assembly-customizing-the-appearance.adoc @@ -4,13 +4,15 @@ [id="{context}"] = Customizing {product} appearance +By modifying the visual aspects of the interface, organizations can align {product} with their branding guidelines and improve the overall user experience. + The following default theme configurations are available for {product}: The {product} theme:: Default theme configurations to make your developer portal look like a standard {product} instance. For more information, see xref:ref-customize-rhdh-default-rhdh_{context}[] The Backstage theme:: Default theme configurations to make your developer portal look like a standard Backstage instance. For more information, see xref:ref-customize-rhdh-default-backstage_{context}[] -You can change or disable particular parameters in a default theme or create a fully customized theme by modifying the `app-config-rhdh.yaml` file. From the `app-config-rhdh.yaml` file, you can customize common theme components, including the following: +You can change or disable particular parameters in a default theme or create a fully customized theme by modifying the `app-config-rhdh.yaml` file. From the `app-config-rhdh.yaml` file, you can customize common theme components, including the following components: * Company name and logo * Font color, size, and style of text in paragraphs, headings, headers, and buttons @@ -25,7 +27,7 @@ include::modules/customizing-the-appearance/proc-customize-rhdh-theme-mode.adoc[ include::modules/customizing-the-appearance/proc-customize-rhdh-branding-logo.adoc[leveloffset=+1] -include::modules/customizing-the-appearance/con-customize-rhdh-sidebar-menuitems.adoc[leveloffset=+1] +include::modules/customizing-the-appearance/con-about-rhdh-sidebar-menuitems.adoc[leveloffset=+1] include::modules/customizing-the-appearance/proc-customize-rhdh-sidebar-menuitems.adoc[leveloffset=+2] diff --git a/modules/configuring-a-floating-action-button/proc-configuring-floating-action-button-as-a-dynamic-plugin.adoc b/modules/configuring-a-floating-action-button/proc-configuring-floating-action-button-as-a-dynamic-plugin.adoc index b96fc945df..4193f14924 100644 --- a/modules/configuring-a-floating-action-button/proc-configuring-floating-action-button-as-a-dynamic-plugin.adoc +++ b/modules/configuring-a-floating-action-button/proc-configuring-floating-action-button-as-a-dynamic-plugin.adoc @@ -14,7 +14,6 @@ To configure a floating action button as a dynamic plugin, complete any of the f * Specify the `global.floatingactionbutton/config` mount point in your `app-config-dynamic.yaml` file. For example: + -.Example of a bulk-import plugin as a floating action button [source,yaml] ---- - package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-bulk-import @@ -26,10 +25,10 @@ To configure a floating action button as a dynamic plugin, complete any of the f # Start of the floating action button configuration mountPoints: - mountPoint: global.floatingactionbutton/config - importName: BulkImportPage # <1> + importName: BulkImportPage config: slot: 'page-end' - icon: # <2> + icon: label: 'Bulk import' toolTip: 'Register multiple repositories in bulk' to: /bulk-import/repositories @@ -44,12 +43,11 @@ To configure a floating action button as a dynamic plugin, complete any of the f icon: bulkImportIcon text: Bulk import ---- -<1> (Required) The import name with an associated component to the mount point. -<2> Use the `svg` value to display a black BulkImportPage icon. +`frontend:mountPoints:importName`:: (Required) The import name with an associated component to the mount point. +`frontend:mountPoints:importName:icon`:: Use the `svg` value to display a black `BulkImportPage` icon. * To configure an action as a floating action button that opens an external link, specify the `global.floatingactionbutton/config` mount point in your `dynamic-plugins.yaml` file within the `backstage-plugin-global-floating-action-button` plugin. For example: + -.Example of a floating action button that opens GitHub [source,yaml] ---- - package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-global-floating-action-button @@ -62,9 +60,9 @@ To configure a floating action button as a dynamic plugin, complete any of the f - mountPoint: application/listener importName: DynamicGlobalFloatingActionButton - mountPoint: global.floatingactionbutton/config - importName: NullComponent # <1> + importName: NullComponent config: - icon: '' # <2> + icon: '' label: 'Quay' showLabel: true toolTip: 'Quay' @@ -77,12 +75,11 @@ To configure a floating action button as a dynamic plugin, complete any of the f toolTip: 'Github' to: https://github.com/redhat-developer/rhdh-plugins ---- -<1> (Required) The import name with an associated component to the mount point. -<2> Use the `svg` value to display the `Quay` icon. +`frontend:mountPoints:importName`:: Enter the import name with an associated component to the mount point. +`frontend:mountPoints:importName:icon`:: (Optional) Enter the icon in Scalable Vector Graphics (SVG) format to display the `Quay` icon. * To configure a floating action button that contains a submenu, define the `global.floatingactionbutton/config` mount point in the same `slot` field in your `dynamic-plugins.yaml` file for multiple actions. The default slot is `page-end` when not specified. For example: + -.Example of a floating action button with submenu actions [source,yaml] ---- - package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-bulk-import @@ -94,7 +91,7 @@ To configure a floating action button as a dynamic plugin, complete any of the f # Start of fab config mountPoints: - mountPoint: global.floatingactionbutton/config - importName: BulkImportPage # <1> + importName: BulkImportPage config: slot: 'page-end' icon: @@ -122,14 +119,14 @@ To configure a floating action button as a dynamic plugin, complete any of the f - mountPoint: application/listener importName: DynamicGlobalFloatingActionButton - mountPoint: global.floatingactionbutton/config - importName: NullComponent # <1> + importName: NullComponent config: icon: github label: 'Git' toolTip: 'Github' to: https://github.com/redhat-developer/rhdh-plugins - mountPoint: global.floatingactionbutton/config - importName: NullComponent # <1> + importName: NullComponent config: icon: '' label: 'Quay' @@ -137,11 +134,10 @@ To configure a floating action button as a dynamic plugin, complete any of the f toolTip: 'Quay' to: 'https://quay.io' ---- -<1> (Required) The import name with an associated component to the mount point. +`frontend:mountPoints:importName`:: (Required) The import name with an associated component to the mount point. * To configure a floating action button to display only on specific pages, configure the `global.floatingactionbutton/config` mount point in the `backstage-plugin-global-floating-action-button` plugin and set the `visibleOnPaths` property as shown in the following example: + -.Example of a floating action button to display specific pages [source,yaml] ---- - package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-bulk-import @@ -172,11 +168,10 @@ To configure a floating action button as a dynamic plugin, complete any of the f icon: bulkImportIcon text: Bulk import ---- -<1> (Required) The import name with an associated component to the mount point. +`frontend:mountPoints:importName`:: Enter the import name with an associated component to the mount point. * To hide a floating action button on specific pages, configure the `global.floatingactionbutton/config` mount point in the `backstage-plugin-global-floating-action-button` plugin and set the `excludeOnPaths` property as shown in the following example: + -.Example of a floating action button to hide specific pages [source,yaml] ---- - package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-bulk-import @@ -207,4 +202,4 @@ To configure a floating action button as a dynamic plugin, complete any of the f icon: bulkImportIcon text: Bulk import ---- -<1> (Required) The import name with an associated component to the mount point. +`frontend:mountPoints:importName`:: Enter the import name with an associated component to the mount point. diff --git a/modules/configuring-the-global-header/con-quicklinks-and-starred-items-in-global-header.adoc b/modules/configuring-the-global-header/con-quicklinks-and-starred-items-in-global-header.adoc index 37412bea07..2c9c33a075 100644 --- a/modules/configuring-the-global-header/con-quicklinks-and-starred-items-in-global-header.adoc +++ b/modules/configuring-the-global-header/con-quicklinks-and-starred-items-in-global-header.adoc @@ -9,8 +9,8 @@ The *Quicklinks* matrix, organized by sections (for example, Documentation or De The default configuration includes the following components: -*StarredDropdown*: Displays the *Starred Items* menu in the global header by default, as shown in the following configuration: - +`StarredDropdown`:: Display the *Starred Items* menu in the global header by default, as shown in the following configuration: ++ [source,yaml] ---- # Group: Global Header @@ -21,8 +21,8 @@ The default configuration includes the following components: priority: 85 ---- -*ApplicationLauncherDropdown*: Provides the *Quicklinks* matrix (application launcher) by default, as shown in the following configuration: - +`ApplicationLauncherDropdown`:: Provide the *Quicklinks* matrix (application launcher) by default, as shown in the following configuration: ++ [source,yaml] ---- # Group: Global Header @@ -33,8 +33,8 @@ The default configuration includes the following components: priority: 82 ---- -*MenuItemLink entries*: Define sections, titles, icons, and links within the *Quicklinks* matrix. The default configuration includes links to the {product-short} documentation and an {product-very-short} Local, as shown in the following configurations: - +`MenuItemLink entries`:: Define sections, titles, icons, and links within the *Quicklinks* matrix. The default configuration includes links to the {product-short} documentation and an {product-very-short} Local, as shown in the following configurations: ++ [source,yaml,subs="+attributes"] ---- - mountPoint: global.header/application-launcher @@ -61,8 +61,4 @@ The default configuration includes the following components: [NOTE] ==== When upgrading from previous versions, the installer does not overwrite your existing `dynamic-plugins.yaml` configuration. If you had not configured *Starred Items* or *Quicklinks* previously, they remain disabled after the upgrade and must be manually enabled. -==== - - - - +==== \ No newline at end of file diff --git a/modules/configuring-the-global-header/proc-configuring-logo-in-the-global-header.adoc b/modules/configuring-the-global-header/proc-configuring-logo-in-the-global-header.adoc index 32e833de39..5dadf9c23f 100644 --- a/modules/configuring-the-global-header/proc-configuring-logo-in-the-global-header.adoc +++ b/modules/configuring-the-global-header/proc-configuring-logo-in-the-global-header.adoc @@ -10,14 +10,12 @@ This component supports the following props, which are provided through configur * `logo`: The base64 encoded logo image. * `to`: The redirect path for when users click the logo is '/catalog'. * `width`: The logo width is optional and defaults to 150 px. -* `height`: The logo height is optional and defaults to 40px. +* `height`: The logo height is optional and defaults to 40 px. .Procedure . To display a custom company logo in the global header, update the configuration with a mount point for `CompanyLogo`: + -.Example: Configuring company logo -+ [source,yaml,subs="+attributes,+quotes"] ---- # ...rest of the global header configuration @@ -50,8 +48,6 @@ red-hat-developer-hub.backstage-plugin-global-header: . (Optional) If you do not provide `logo` props to the `CompanyLogo` component, the component instead uses values defined under `app.branding` in your `{my-app-config-file}` file. You can configure the `CompanyLogo` as shown in the following configuration: + -.Example: Fallback configuration -+ [source,yaml,subs="+attributes,+quotes"] ---- app: diff --git a/modules/configuring-the-global-header/proc-customize-rhdh-global-header.adoc b/modules/configuring-the-global-header/proc-customize-rhdh-global-header.adoc index c1938c2232..0635f06c3e 100644 --- a/modules/configuring-the-global-header/proc-customize-rhdh-global-header.adoc +++ b/modules/configuring-the-global-header/proc-customize-rhdh-global-header.adoc @@ -6,8 +6,6 @@ You can use the `red-hat-developer-hub.backstage-plugin-global-header` dynamic plugin to extend the global header with additional buttons and customize the order and position of icons and features. Additionally, you can create and integrate your custom dynamic header plugins using the mount points provided by this new header feature, allowing you to further tailor to suit your needs. For more information about enabling dynamic plugins, see {installing-and-viewing-plugins-book-link}[{installing-and-viewing-plugins-book-title}]. -.Default global header configuration - [source,yaml,subs="+attributes,+quotes"] ---- - package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-global-header @@ -15,11 +13,11 @@ For more information about enabling dynamic plugins, see {installing-and-viewing pluginConfig: app: sidebar: - search: false <1> - settings: false <2> + search: false + settings: false dynamicPlugins: frontend: - default.main-menu-items: <3> + default.main-menu-items: menuItems: default.create: title: '' @@ -76,12 +74,22 @@ For more information about enabling dynamic plugins, see {installing-and-viewing config: priority: 10 ---- -<1> *search*: Hides the *Search* modal in the sidebar menu. Change it to `true` to display the *Search* modal in the sidebar. -<2> *settings*: Hides the *Settings* button in the sidebar menu. Change it to `true` to display the *Settings* button in the sidebar. -<3> `default.main-menu-items`: Hides the *Self-service* button from the sidebar menu. Remove this field to display the *Self-service* button in the sidebar. -<4> *position*: Defines the position of the header. Options: `above-main-content` or `above-sidebar`. +where: + +`search`:: +Enter `false` to hide the *Search* modal in the sidebar menu. +Enter `true` to display the *Search* modal in the sidebar menu. +`settings`:: +Enter `false` to hides the *Settings* button in the sidebar menu. +Enter `true` to display the *Settings* button in the sidebar menu. +`default.main-menu-items`:: +Enter this field to hide the *Self-service* button from the sidebar menu. +Remove this field to display the *Self-service* button in the sidebar menu. +`position`:: +Enter `above-main-content` to position the header above the main content. +Enter `above-sidebar` to position the header above the sidebar. -To extend the functionality of the default global header, include any the following attributes in your global header entry: +To extend the functionality of the default global header, include any of the following attributes in your global header entry: `mountPoint`:: Specifies the location of the header. Use `application/header` to specify it as a global header. You can configure several global headers at different positions by adding entries to the `mountPoints` field. diff --git a/modules/configuring-the-global-header/proc-displaying-preferred-username-in-profile-drop-down.adoc b/modules/configuring-the-global-header/proc-displaying-preferred-username-in-profile-drop-down.adoc index a1218039f6..c29d8e0a5c 100644 --- a/modules/configuring-the-global-header/proc-displaying-preferred-username-in-profile-drop-down.adoc +++ b/modules/configuring-the-global-header/proc-displaying-preferred-username-in-profile-drop-down.adoc @@ -6,8 +6,9 @@ You can display the preferred username in the global header profile drop-down list by configuring `spec.profile.displayName` in the user entity. When not configured, the application falls back to a `metadata.title`. If neither is configured, it defaults to a user-friendly name generated by the `useProfileInfo` hook. .Procedure -.Example when you configure `spec.profile.displayName` +. To configure `spec.profile.displayName`, use the following code: ++ [source,yaml,subs="+attributes,+quotes"] ---- apiVersion: backstage.io/v1alpha1 @@ -24,8 +25,8 @@ spec: memberOf: [janus-authors] ---- -.Example when you do not configure `spec.profile.displayname` but configure `metadata.title` - +. To configure `metadata.title` rather than `spec.profile.displayname`, use the following code: ++ [source,yaml,subs="+attributes,+quotes"] ---- apiVersion: backstage.io/v1alpha1 @@ -39,8 +40,8 @@ spec: memberOf: [janus-authors] ---- -.Example when you do not configure the `spec.profile.displayname` and the `metadata.title` - +. To configure neither `spec.profile.displayname` or `metadata.title`, use the following code: ++ [source,yaml,subs="+attributes,+quotes"] ---- apiVersion: backstage.io/v1alpha1 diff --git a/modules/configuring-the-global-header/proc-enabling-logo-in-the-sidebar.adoc b/modules/configuring-the-global-header/proc-enabling-logo-in-the-sidebar.adoc index 5da014725b..e51e556148 100644 --- a/modules/configuring-the-global-header/proc-enabling-logo-in-the-sidebar.adoc +++ b/modules/configuring-the-global-header/proc-enabling-logo-in-the-sidebar.adoc @@ -7,9 +7,7 @@ You can configure a logo in the sidebar of the {product} ({product-very-short}). .Procedure -. To display the logo in the sidebar, set the value of the `app.sidebar.logo` parameter to `true` as shown in the following example: -+ -.Example: Enabling the logo in only the sidebar +. To display the logo exclusively in the sidebar, set the value of the `app.sidebar.logo` parameter to `true` as shown in the following example: + [source,yaml,subs="+attributes,+quotes"] ---- @@ -25,8 +23,6 @@ To display the logo in only the sidebar, remove the `CompanyLogo` component from . To display the same logo in the sidebar for all themes, update the configuration as shown in the following configuration: + -.Example: Configuring sidebar logo for all themes -+ [source,yaml,subs="+attributes,+quotes"] ---- app: @@ -39,8 +35,6 @@ app: . For theme-specific logos, you can configure the sidebar logo as shown in the following configuration: + -.Example: Theme-specific logos -+ [source,yaml,subs="+attributes,+quotes"] ---- app: @@ -55,4 +49,4 @@ app: .Verification . The logo appears correctly in the sidebar. -. Toggle between `light` and `dark` themes to ensure the correct logo loads in each. +. Toggle between `light` and `dark` themes to ensure the correct logo loads in each. \ No newline at end of file diff --git a/modules/configuring-the-global-header/ref-mount-points.adoc b/modules/configuring-the-global-header/ref-mount-points.adoc index 329857ae9c..319140045b 100644 --- a/modules/configuring-the-global-header/ref-mount-points.adoc +++ b/modules/configuring-the-global-header/ref-mount-points.adoc @@ -5,13 +5,14 @@ You can customize the application header in {product-short} using mount points for dynamic plugins. These mount points give flexibility in configuring the position of the header, its components and dropdown menus. You can create a customized experience with the following enhancements: -application/header:: +`application/header`:: Controls the header position. Use `config.position` to set placement as either `above-main-content` or `above-sidebar`. -global.header/component:: +`global.header/component`:: Configures header components. Use `config.priority` to set the order of components, and pass properties (including CSS styles) via `config.props`. + +`Self-service button`:: + -.Example adding a *Self-service* button [source,yaml,subs="attributes,quotes"] ---- - mountPoint: global.header/component @@ -23,8 +24,9 @@ Configures header components. Use `config.priority` to set the order of componen icon: add to: create ---- + +`Spacer element`:: + -.Example adding a spacer element [source,yaml] ---- - mountPoint: global.header/component @@ -34,8 +36,9 @@ Configures header components. Use `config.priority` to set the order of componen props: growFactor: 0 ---- + +`Divider element`:: + -.Example adding a divider element [source,yaml] ---- mountPoints: @@ -45,10 +48,12 @@ mountPoints: priority: 150 ---- -global.header/profile:: +`global.header/profile`:: Configures the profile dropdown list when the `ProfileDropdown` component is enabled. + + +* To add a settings link to the profile dropdown, use the following code: + -.Example adding a settings link to the profile dropdown [source,yaml] ---- - mountPoint: global.header/profile @@ -63,8 +68,9 @@ Configures the profile dropdown list when the `ProfileDropdown` component is ena global.header/create:: Configures the create dropdown list when the `CreateDropdown` component is enabled. + +* To add a section for registering a component, use the following code: + -.Example adding a section for registering a component [source,yaml] ---- - mountPoint: global.header/create diff --git a/modules/configuring-the-quickstarts/proc-customize-rhdh-quickstart.adoc b/modules/configuring-the-quickstarts/proc-customize-rhdh-quickstart.adoc index fd62a8ee89..8746198c22 100644 --- a/modules/configuring-the-quickstarts/proc-customize-rhdh-quickstart.adoc +++ b/modules/configuring-the-quickstarts/proc-customize-rhdh-quickstart.adoc @@ -9,19 +9,18 @@ As an administrator, you can configure the {product} Quickstart plugin to create You must have administrator permissions. .Procedure -. Update your `{my-app-config-file}`, as follows: +. Update your `{my-app-config-file}`, as shown in the following code: + -.Example of a customized Quickstart plugin [source,yaml,subs="+attributes"] ---- app: quickstart: - - title: 'Welcome to {product-short}' # <1> - description: 'Learn the basics of navigating the {product-short} interface' # <2> - icon: 'home' # <3> + - title: 'Welcome to {product-short}' + description: 'Learn the basics of navigating the {product-short} interface' + icon: 'home' cta: - text: 'Get Started' # <4> - link: '/catalog' # <5> + text: 'Get Started' + link: '/catalog' - title: 'Create Your First Component' description: 'Follow our guide to register your first software component' icon: 'code' @@ -35,23 +34,9 @@ app: text: 'Browse Templates' link: '/create' ---- -<1> (Required) The display title for the quickstart step. -<2> (Required) A brief description of what the step covers. -<3> Icon identifier (supports Material UI icons). -<4> CTA button text. -<5> CTA target URL or route. - -== Disabling the Quickstart plugin -The Quickstart plugin is pre-loaded in {product-short} with basic configuration properties, and enabled by default. To disable it, set the disabled property to `true` as follows: - -.Example of disabling the Quickstart plugin -[source,yaml] ----- -global: - dynamic: - includes: - - dynamic-plugins.default.yaml - plugins: - - package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-quickstart - disabled: true ----- +`title`:: Enter the display title for the quickstart step. +`description`:: Enter the brief description of what the step covers. +`icon`:: (Optional) Enter the icon identifier (supports Material UI icons). +`cta`:: +`text`::: (Optional) Enter the CTA button text. +`link`::: (Optional) Enter the CTA target URL or route. \ No newline at end of file diff --git a/modules/configuring-the-quickstarts/proc-disabling-rhdh-quickstart.adoc b/modules/configuring-the-quickstarts/proc-disabling-rhdh-quickstart.adoc new file mode 100644 index 0000000000..ad0b7acf78 --- /dev/null +++ b/modules/configuring-the-quickstarts/proc-disabling-rhdh-quickstart.adoc @@ -0,0 +1,20 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-disabling-rhdh-quickstart_{context}"] += Disabling the Quickstart plugin +The Quickstart plugin is pre-loaded in {product-short} with basic configuration properties, and enabled by default. + +.Procedure + +* To disable the Quickstart plugin, set the disabled property to `true` as shown in the following code: ++ +[source,yaml] +---- +global: + dynamic: + includes: + - dynamic-plugins.default.yaml + plugins: + - package: ./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-quickstart + disabled: true +---- diff --git a/modules/customizing-the-appearance/con-customize-rhdh-sidebar-menuitems.adoc b/modules/customizing-the-appearance/con-about-rhdh-sidebar-menuitems.adoc similarity index 81% rename from modules/customizing-the-appearance/con-customize-rhdh-sidebar-menuitems.adoc rename to modules/customizing-the-appearance/con-about-rhdh-sidebar-menuitems.adoc index fc1b5c8e8d..6b60ba8d8e 100644 --- a/modules/customizing-the-appearance/con-customize-rhdh-sidebar-menuitems.adoc +++ b/modules/customizing-the-appearance/con-about-rhdh-sidebar-menuitems.adoc @@ -1,7 +1,7 @@ :_mod-docs-content-type: CONCEPT -[id="con-customize-rhdh-sidebar-menuitems_{context}"] -= Customizing the sidebar menu items for your {product-short} instance +[id="con-about-rhdh-sidebar-menuitems_{context}"] += About the sidebar menu items for your {product-short} instance The sidebar menu in {product} consists of two main parts that you can configure: diff --git a/modules/customizing-the-appearance/proc-configuring-dynamic-plugin-menuitem.adoc b/modules/customizing-the-appearance/proc-configuring-dynamic-plugin-menuitem.adoc index 8302eba848..b0005308fd 100644 --- a/modules/customizing-the-appearance/proc-configuring-dynamic-plugin-menuitem.adoc +++ b/modules/customizing-the-appearance/proc-configuring-dynamic-plugin-menuitem.adoc @@ -13,30 +13,43 @@ Configure a dynamic plugin menu item using the following step: ---- dynamicPlugins: frontend: - __: # <1> + __: menuItems: - : # <2> - icon: # home | group | category | extension | school | __ # <3> - title: __ # <4> - priority: 10 # <5> - parent: favorites # <6> - enabled: true # <7> + : + icon: # home | group | category | extension | school | __ + title: __ + priority: 10 + parent: favorites + enabled: true ---- -<1> `__`: Enter the plugin name. This name is the same as the `scalprum.name` key in the `package.json` file. -<2> `__`: Enter a unique name in the main sidebar navigation for either a standalone menu item or a parent menu item. If this field specifies a plugin menu item, the name of the menu item must match the name using in the corresponding path in `dynamicRoutes`. For example, if `dynamicRoutes` defines `path: /my-plugin`, then `menu_item_name` must be defined as `my-plugin`. -<3> `icon`: (Optional) Enter the icon name. You can use any of the following icons: +`__`:: +Enter the plugin name. This name is the same as the `scalprum.name` key in the `package.json` file. + +`__`:: +Enter a unique name in the main sidebar navigation for either a standalone menu item or a parent menu item. If this field specifies a plugin menu item, the name of the menu item must match the name using in the corresponding path in `dynamicRoutes`. For example, if `dynamicRoutes` defines `path: /my-plugin`, then `menu_item_name` must be defined as `my-plugin`. + +`icon`:: +(Optional) Enter the icon name. You can use any of the following icons: ** Default icons, such as `home`, `group`, `category`, `extension`, and `school`. To use default icons, set the icon as an (`" "`) empty string. ** A custom icon, where __ specifies the name of your custom icon ** An SVG icon, such as: `icon: ...` ** An HTML image, such as: `icon: https://img.icons8.com/ios-glyphs/20/FFFFFF/shop.png` -<4> `title`: (Optional) Enter the menu item title. Omit it when the title is already specified in the `dynamicRoutes` configuration under `menuItem.text`. To hide the title from the sidebar, set the title as an (`" "`) empty string. + +`title`:: +(Optional) Enter the menu item title. Omit it when the title is already specified in the `dynamicRoutes` configuration under `menuItem.text`. To hide the title from the sidebar, set the title as an (`" "`) empty string. // Update <4> for release 1.6 as this option (currently a workaround) would be added as a functionality. RHIDP-6333. -<5> `priority`: (Optional) Sets the order in which menu items appear in the sidebar. The default priority is 0, which places the item at the bottom of the list. A higher priority value places the item higher in the sidebar. You can define this field for each section. -<6> `parent`: (Optional) Enter the parent menu item under which the current item is nested. If this field is used, the parent menu item must be defined elsewhere in the `menuItems` configuration of any enabled plugin. You can define this field for each section. -<7> `enabled`: (Optional) If this field is used to hide the menu item from the sidebar, set the value to `false`. To display the menu item in the sidebar, set the value to `true`. + +`priority`:: +(Optional) Enter an integer value to set the order in which menu items appear in the sidebar. + +`parent`:: +(Optional) Enter the parent menu item under which the current item is nested. If this field is used, the parent menu item must be defined elsewhere in the `menuItems` configuration of any enabled plugin. You can define this field for each section. + +`enabled`:: +(Optional) Enter `false` to hide the menu item from the sidebar. +Enter `true` to display the menu item in the sidebar. + -.Example `menuItems` configuration [source,yaml,subs="+attributes"] ---- dynamicPlugins: @@ -50,18 +63,25 @@ dynamicPlugins: icon: fooIcon text: Foo Plugin Page menuItems: - my-plugin: # <1> - priority: 10 # <2> - parent: favorites # <3> - favorites: # <4> - icon: favorite # <5> - title: Favorites # <6> - priority: 100 # <7> + my-plugin: + priority: 10 + parent: favorites + favorites: + icon: favorite + title: Favorites + priority: 100 ---- -<1> `my-plugin`: Matches the value of the `path` field in `dynamicRoutes`. -<2> `priority`: Controls order of plugins under the parent menu item. -<3> `parent`: Nests this plugin under the `favorites` parent menu item. -<4> `favorites`: Configuration for the parent menu item. -<5> `icon`: Displays the `favorite` icon from the {product-very-short} system icons. -<6> `title`: Displays the title name for the parent menu item. -<7> `priority`: Order of the `favourites` menu item in the sidebar. +`my-plugin`:: +Enter the value of the `path` field in `dynamicRoutes`. + +`priority`:: +Enter an integer value to set the order in which plugins appear in the parent menu item. + +`parent`:: +Enter the parent menu item id to nest this plugin under, such as `favorites`. + +`favorites`:: +Configuration for the parent menu item. + +`title`:: +Displays the title name for the parent menu item. \ No newline at end of file diff --git a/modules/customizing-the-appearance/proc-customize-rhdh-branding-logo.adoc b/modules/customizing-the-appearance/proc-customize-rhdh-branding-logo.adoc index ff48955707..ff6f61e507 100644 --- a/modules/customizing-the-appearance/proc-customize-rhdh-branding-logo.adoc +++ b/modules/customizing-the-appearance/proc-customize-rhdh-branding-logo.adoc @@ -12,11 +12,8 @@ app: fullLogo: ${BASE64_EMBEDDED_FULL_LOGO} <1> iconLogo: ${BASE64_EMBEDDED_ICON_LOGO} <2> ---- - -where: - -<1> `fullLogo` is the logo on the expanded (pinned) sidebar and expects a base64 encoded image. -<2> `iconLogo` is the logo on the collapsed (unpinned) sidebar and expects a base64 encoded image. +`fullLogo`:: Enter the logo on the expanded (pinned) sidebar as a base64 encoded image. +`iconLogo`:: Enter the logo on the collapsed (unpinned) sidebar as a base64 encoded image. + You can format the `BASE64_EMBEDDED_FULL_LOGO` environment variable as follows: + @@ -40,9 +37,7 @@ You can also customize the width of the branding logo by setting a value for the ---- app: branding: - fullLogoWidth: 110px <1> + fullLogoWidth: 110px # ... ---- - -<1> The default value for the logo width is `110px`. The following units are supported: `integer`, `px`, `em`, `rem`, percentage. - +`fullLogoWidth`:: The default value for the logo width is `110px`. The following units are supported: `integer`, `px`, `em`, `rem`, percentage. \ No newline at end of file diff --git a/modules/customizing-the-appearance/proc-customize-rhdh-page-theme.adoc b/modules/customizing-the-appearance/proc-customize-rhdh-page-theme.adoc index 9ceff8964e..1d2dd9016d 100644 --- a/modules/customizing-the-appearance/proc-customize-rhdh-page-theme.adoc +++ b/modules/customizing-the-appearance/proc-customize-rhdh-page-theme.adoc @@ -10,14 +10,14 @@ You can customize the header color for the light and dark theme modes in your {p app: branding: theme: - light: <1> + light: palette: {} pageTheme: - default: <2> - backgroundColor: "" <3> - fontColor: "" <4> - shape: none <5> - apis: <6> + default: + backgroundColor: "" + fontColor: "" + shape: none + apis: backgroundColor: "" fontColor: "" shape: none @@ -30,13 +30,12 @@ app: shape: none # ... ---- - -<1> The theme mode, for example, `light` or `dark` -<2> The `yaml` header for the default page theme configuration -<3> The color of the page header background, for example, `#ffffff` or `white` -<4> The color of the text in the page header, for example, `#000000` or `black` -<5> The pattern on the page header, for example, `wave`, `round`, or `none` -<6> The `yaml` header for a specific page theme configuration, for example, `apis`, `home` +`light`:: Enter the theme mode, such as `light` or `dark`. +`default`:: Enter the default page theme configuration +`backgroundColor`::: Enter the page header background color, such as `#ffffff` or `white`. +`fontColor`::: Enter the page header text color, such as `#000000` or `black`. +`shape`::: Enter the page header pattern, such as `wave`, `round`, or `none`. +`apis::` Enter the page id to configure, such as `apis` or `home`. //The page theme name depends on the plugin that you are customizing the page header for. //can include information about this topic in the future. diff --git a/modules/customizing-the-appearance/proc-customize-rhdh-palette.adoc b/modules/customizing-the-appearance/proc-customize-rhdh-palette.adoc index 94214bfdd8..f268ccbfd6 100644 --- a/modules/customizing-the-appearance/proc-customize-rhdh-palette.adoc +++ b/modules/customizing-the-appearance/proc-customize-rhdh-palette.adoc @@ -13,30 +13,27 @@ app: light: palette: primary: - main: <1> + main: navigation: - indicator: <2> + indicator: pageTheme: default: - backgroundColor: [, ] <3> + backgroundColor: [, ] dark: palette: primary: - main: <4> + main: navigation: - indicator: <5> + indicator: pageTheme: default: - backgroundColor: [, ] <6> + backgroundColor: [, ] # ... ---- - -<1> The main primary color for the light color palette, for example, `#ffffff` or `white` -<2> The color of the navigation indicator for the light color palette, which is a vertical bar that indicates the selected tab in the navigation panel, for example, `#FF0000` or `red` -<3> The background color for the default page theme for the light color palette, for example, `#ffffff` or `white` -<4> The main primary color for the dark color palette, for example, `#000000` or `black` -<5> The color of the navigation indicator for the dark color palette, which is a vertical bar that indicates the selected tab in the navigation panel, for example, `#FF0000` or `red` -<6> The background color for the default page theme for the dark color palette, for example, `#000000` or `black` +`light|dark`:: Enter the theme name: `light` or `dark`. +`palette.primary:main`::: Enter the palette main primary color, such as `#ffffff` or `white`. +`palette.navigation:indicator`::: Enter the palette navigation indicator color, which is a vertical bar that indicates the selected tab in the navigation panel, such as `#FF0000` or `red`. +`pageTheme:default:backgroundColor`::: Enter the default page theme background color, such as `#ffffff` or `white`. .Additional resources * xref:proc-customizing-rhdh-theme-mode_{context}[] diff --git a/modules/customizing-the-appearance/proc-customize-rhdh-theme-mode.adoc b/modules/customizing-the-appearance/proc-customize-rhdh-theme-mode.adoc index 568caf4b9f..9b8cebd1b4 100644 --- a/modules/customizing-the-appearance/proc-customize-rhdh-theme-mode.adoc +++ b/modules/customizing-the-appearance/proc-customize-rhdh-theme-mode.adoc @@ -3,18 +3,18 @@ [id="proc-customizing-rhdh-theme-mode_{context}"] = Customizing the theme mode for your {product-short} instance +You can choose one of the following theme modes for your {product-short} instance: + +* *Light* +* *Dark* +* *Auto* + [NOTE] ==== In {product-short}, theme configurations are used to change the look and feel of different UI components. So, you might notice changes in different UI components, such as buttons, tabs, sidebars, cards, and tables along with some changes in background color and font used on the {product-very-short} pages. ==== -You can choose one of the following theme modes for your {product-short} instance: - -* Light theme -* Dark theme -* Auto - -The default theme mode is Auto, which automatically sets the light or dark theme based on your system preferences. +The default theme mode is *Auto*, which automatically sets the light or dark theme based on your system preferences. .Prerequisites @@ -23,7 +23,7 @@ The default theme mode is Auto, which automatically sets the light or dark theme .Procedure . From the {product-short} web console, click *Settings*. -. From the *Appearance* panel, click *LIGHT THEME*, *DARK THEME*, or *AUTO* to change the theme mode. +. From the *Appearance* panel, click *Light*, *Dark*, or *Auto* to change the theme mode. + image::user-guide/custom-theme-mode-1.png[] diff --git a/modules/customizing-the-appearance/proc-loading-custom-theme-using-dynamic-plugin.adoc b/modules/customizing-the-appearance/proc-loading-custom-theme-using-dynamic-plugin.adoc index a373d9ead1..d7ffb80fc7 100644 --- a/modules/customizing-the-appearance/proc-loading-custom-theme-using-dynamic-plugin.adoc +++ b/modules/customizing-the-appearance/proc-loading-custom-theme-using-dynamic-plugin.adoc @@ -30,13 +30,15 @@ dynamicPlugins: frontend: example.my-custom-theme-plugin: themes: - - id: light # <1> + - id: light title: Light variant: light icon: someIconReference importName: lightThemeProvider ---- -<1> Set your theme ID by specifying the desired value. Optionally, override the default {product-short} themes by using the following ID values: `light` to replace the default light theme, or `dark` to replace the default dark theme. +`id`:: Enter your theme ID, such as `my_theme`. +Enter `dark` to override the default {product-short} dark theme. +Enter `light` to override the default {product-short} light theme. .Verification diff --git a/modules/customizing-the-appearance/proc-modifying-or-adding-rhdh-custom-menuitem.adoc b/modules/customizing-the-appearance/proc-modifying-or-adding-rhdh-custom-menuitem.adoc index dc67acbac9..e376098969 100644 --- a/modules/customizing-the-appearance/proc-modifying-or-adding-rhdh-custom-menuitem.adoc +++ b/modules/customizing-the-appearance/proc-modifying-or-adding-rhdh-custom-menuitem.adoc @@ -14,59 +14,58 @@ dynamicPlugins: frontend: default.main-menu-items: menuItems: - default.__: # <1> - icon: # home | group | category | extension | school | __ # <2> - title: __ # <3> - priority: 10 # <4> - default.__: # <5> - parent: __ # <6> - icon: # home | group | category | extension | school | __ # <7> - title: __ # <8> - to: __ # <9> - priority: 100 # <10> - enabled: true # <11> + default.__: + icon: # home | group | category | extension | school | __ + title: __ + priority: 10 + default.__: + parent: __ + icon: # home | group | category | extension | school | __ + title: __ + to: __ + priority: 100 + enabled: true ---- -<1> `default.__`: (Optional) Enter the menu group parent item name to configure static main menu items. If no `default.__` has a `parent` value set, this field is not needed. -<2> `icon`: Enter the menu icon. Required for parent menu items. -<3> `title`: Enter the menu group title. Required for parent menu items. -<4> `priority`: (Optional) Enter the order of this menu item within its menu level. -<5> `default.__`: Enter the menu item name for which you want to override the default value. Add the `default.` prefix to identify a main menu item. -<6> `parent`: (Optional) Enter the parent menu item for this item. Required if is specified as the child of any menu items. -<7> `icon`: (Optional) Enter the menu icon. To use the default icon, set the icon as an (`" "`) empty string. -<8> `title`: (Optional) Enter the menu group title. Only required for adding a new custom main menu item. To hide a default main menu item title from the sidebar, set the title as an (`" "`) empty string. +`default.__`:: (Optional) Enter the menu group parent item name to configure static main menu items. If no `default.__` has a `parent` value set, this field is not needed. +`icon`:: Enter the menu icon. Required for parent menu items. +`title`:: Enter the menu group title. Required for parent menu items. +`priority`:: (Optional) Enter the order of this menu item within its menu level. +`default.__`:: Enter the menu item name for which you want to override the default value. Add the `default.` prefix to identify a main menu item. +`parent`:: (Optional) Enter the parent menu item for this item. Required if is specified as the child of any menu items. +`icon`:: (Optional) Enter the menu icon. To use the default icon, set the icon as an (`" "`) empty string. +`title`:: (Optional) Enter the menu group title. Only required for adding a new custom main menu item. To hide a default main menu item title from the sidebar, set the title as an (`" "`) empty string. // Update <8> for release 1.6 as this option (currently a workaround) would be added as a functionality. RHIDP-6333. -<9> `to`: (Optional) Enter the path that the menu item navigates to. If it is not set, it defaults to the home page. -<10> `priority`: (Optional) Enter the order of this menu item within its menu level. -<11> `enabled`: (Optional) If this field is used to display the menu item in the sidebar, set the value to `true`. To hide the menu item from the sidebar, set the value to `false`. +`to`:: (Optional) Enter the path that the menu item navigates to. If it is not set, it defaults to the home page. +`priority`:: (Optional) Enter the order of this menu item within its menu level. +`enabled`:: (Optional) If this field is used to display the menu item in the sidebar, set the value to `true`. To hide the menu item from the sidebar, set the value to `false`. + -.Example `mainItems` configuration [source,yaml] ---- default.main-menu-items: menuItems: default.catalog: - icon: category # <1> + icon: category title: My Catalog priority: 5 default.learning-path: - title: '' # <2> - default.parentlist: # <3> + title: '' + default.parentlist: title: Overview icon: bookmarks default.home: - parent: default.parentlist # <4> + parent: default.parentlist default.references: - title: References # <5> - icon: school # <6> - to: /references # <7> - enabled: true # <8> + title: References + icon: school + to: /references + enabled: true ---- -<1> `icon`: Specifies if you want to change the icon default menu item for the catalog. -<2> `title`: Specifies an empty string `" "` to hide the learning path from the default sidebar. -<3> `default.parentlist`: Introduces the parent menu item. -<4> `parent`: Nests home menu under the `default.parentlist` parent menu item. -<5> `title`: Specifies a name for `default.references` -<6> `icon`: Displays the `school` icon. -<7> `to`: Redirects `default.references` to the `/references` page. -<8> `enabled`: (Optional) If this field is used to display the menu item in the sidebar, set the value to `true`. To hide the menu item from the sidebar, set the value to `false`. +`icon`:: (Optional) Enter the icon name, such as `category`, bookmarks`, `school`, etc. to change the default icon. +`title`:: Enter an empty string `''` to hide the learning path from the default sidebar. +`default.parentlist`:: Enter the parent menu items. +`parent`:: Enter the parent menu under which to nest the the menu entry, such as `default.parentlist`. +`title`:: Enter the menu entry name, such as `My Catalog`, `Overview` or `References`. +`to`:: Enter the page to redirect to. For example, `default.references` redirects to the `/references` page. +`enabled`:: (Optional) Enter `true` to display the menu item in the sidebar. +Enter `false` to hide the menu item from the sidebar. \ No newline at end of file diff --git a/modules/customizing-the-home-page/proc-customizing-the-home-page-cards.adoc b/modules/customizing-the-home-page/proc-customizing-the-home-page-cards.adoc index 135a660ebc..c3c123a540 100644 --- a/modules/customizing-the-home-page/proc-customizing-the-home-page-cards.adoc +++ b/modules/customizing-the-home-page/proc-customizing-the-home-page-cards.adoc @@ -84,6 +84,9 @@ dynamicPlugins: sm: { w: 10, h: 1, x: 1 } xs: { w: 12, h: 1 } xxs: { w: 12, h: 1 } + props: + path: /search + queryParam: query ---- .Available props @@ -121,6 +124,9 @@ dynamicPlugins: sm: { h: 8 } xs: { h: 8 } xxs: { h: 8 } + props: + title: Quick Access + path: /quickaccess ---- .Available props diff --git a/modules/customizing-the-quick-access-card/proc-using-a-dedicated-service-to-provide-data-to-the-quick-access-card.adoc b/modules/customizing-the-quick-access-card/proc-using-a-dedicated-service-to-provide-data-to-the-quick-access-card.adoc index 94f1ae97b7..7d5603d8a2 100644 --- a/modules/customizing-the-quick-access-card/proc-using-a-dedicated-service-to-provide-data-to-the-quick-access-card.adoc +++ b/modules/customizing-the-quick-access-card/proc-using-a-dedicated-service-to-provide-data-to-the-quick-access-card.adoc @@ -18,7 +18,7 @@ For more information, see {installing-on-ocp-book-link}#assembly-install-rhdh-oc To use a separate service to provide the Home page data, complete the following steps: -. From the *Developer* perspective in the {ocp-brand-name} web console, click *+Add* > *Import from Git*. +. In the {ocp-brand-name} web console, click *+Add* > *Import from Git*. . Enter the URL of your Git repository into the *Git Repo URL* field. + -- @@ -59,7 +59,7 @@ The `red-hat-developer-hub-customization-provider` service contains the 8080 por . Delete the {product-short} pod to ensure that the new configurations are loaded correctly. .Verification -* To view the service, go to the *Administrator* perspective in the {ocp-short} web console and click *Networking* > *Service*. +* To view the service, go to the {ocp-short} web console and click *Networking* > *Service*. + [NOTE] ==== @@ -107,7 +107,7 @@ If the request call fails or is not configured, the {product-short} instance fal ==== * If the images or icons do not load, then allowlist them by adding your image or icon host URLs to the content security policy (csp) `img-src` in your custom ConfigMap as shown in the following example: - ++ [source,yaml,subs="attributes+"] ---- kind: ConfigMap diff --git a/modules/software-catalogs/proc-registering-components-manually-in-the-rhdh-instance.adoc b/modules/software-catalogs/proc-registering-components-manually-in-the-rhdh-instance.adoc index 110d8056a0..da00660c6e 100644 --- a/modules/software-catalogs/proc-registering-components-manually-in-the-rhdh-instance.adoc +++ b/modules/software-catalogs/proc-registering-components-manually-in-the-rhdh-instance.adoc @@ -14,7 +14,6 @@ To manually register components in your {product-very-short} instance, create a . In the root directory of your software project, create a file named `catalog-info.yaml`. + -.Example of a `catalog-info.yaml` file [source,yaml] ---- apiVersion: backstage.io/v1alpha1 diff --git a/modules/software-catalogs/proc-starring-components-in-the-software-catalog.adoc b/modules/software-catalogs/proc-starring-components-in-the-software-catalog.adoc index 01a1ed3050..b09d242145 100644 --- a/modules/software-catalogs/proc-starring-components-in-the-software-catalog.adoc +++ b/modules/software-catalogs/proc-starring-components-in-the-software-catalog.adoc @@ -11,7 +11,7 @@ You can use the *Add to favorites* icon to add the software catalogs that you vi To quickly access the Software Catalogs that you visit regularly, complete the following steps: . In your {product} navigation menu, click *Catalog*. -. Find the software component that you want to add as a favorite, then click the *Add to favorites* icon under *Actions*. +. Find the software component that you want to add as a favorite, under *Actions*, click the *Add to favorites* icon. .Verification diff --git a/modules/software-catalogs/proc-updating-components-in-the-software-catalog.adoc b/modules/software-catalogs/proc-updating-components-in-the-software-catalog.adoc index 5f460b1903..03e219c0a6 100644 --- a/modules/software-catalogs/proc-updating-components-in-the-software-catalog.adoc +++ b/modules/software-catalogs/proc-updating-components-in-the-software-catalog.adoc @@ -15,7 +15,7 @@ You can update components in the Software Catalog in your {product} instance. To update components in the Software Catalog in your {product} instance, complete the following steps: . In your {product} navigation menu, click *Catalog*. -. Find the software component that you want to edit, then click the *Edit* icon under *Actions*. +. Find the software component that you want to edit, under *Actions*, click the *Edit* icon. + [NOTE] diff --git a/modules/software-catalogs/proc-viewing-software-catalog-yaml.adoc b/modules/software-catalogs/proc-viewing-software-catalog-yaml.adoc index 607cae63d3..1e2fac516e 100644 --- a/modules/software-catalogs/proc-viewing-software-catalog-yaml.adoc +++ b/modules/software-catalogs/proc-viewing-software-catalog-yaml.adoc @@ -10,7 +10,7 @@ You can view the Software Catalog YAML file in your {product} instance. The YAML To view the Software Catalog YAML file in your {product} instance, complete the following steps: . In your {product} navigation menu, click *Catalog*. -. Find the software component that you want to view, then click the *View* icon under *Actions*. +. Find the software component that you want to view, under *Actions*, click the *View* icon. + [NOTE] From b1982f5c62d9b4af70d1eeb6b6a545915bdd473f Mon Sep 17 00:00:00 2001 From: Judith Magak <124673476+jmagak@users.noreply.github.com> Date: Thu, 9 Oct 2025 16:18:54 +0200 Subject: [PATCH 18/22] RHIDP-8641: Building and Deploying Serverless Workflows (#1416) * Minor fix Building and Deploying Serverless Workflows Building and Deploying Serverless Workflows Building and Deploying Serverless Workflows Building and Deploying Serverless Workflows Building and Deploying Serverless Workflows Building and Deploying Serverless Workflows Apply Piotr and Yona's suggestions Make adjustments Apply technical reviewers suggestions Update link Update the build and deploy section Minor fix Building and Deploying Serverless Workflows * Minor update * Apply Priyanka's suggestions * Apply Priyanka's suggestions * Apply Priyanka's suggestions --------- Co-authored-by: GitHub Actions --- artifacts/attributes.adoc | 2 + ...ng-and-deploying-serverless-workflows.adoc | 45 ++++++++ .../con-benefits-of-workflow-images.adoc | 11 ++ .../con-build-sh-script-and-its-uses.adoc | 35 ++++++ ...riables-supported-by-the-build-script.adoc | 28 +++++ .../con-generated-workflow-manifests.adoc | 76 ++++++++++++ .../con-project-structure-overview.adoc | 32 ++++++ modules/orchestrator/con-required-tools.adoc | 17 +++ .../orchestrator/proc-building-locally.adoc | 35 ++++++ .../proc-building-the-01-basic-workflow.adoc | 27 +++++ .../proc-creating-and-running-workflows.adoc | 43 +++++++ ...proc-deploying-workflows-on-a-cluster.adoc | 108 ++++++++++++++++++ titles/orchestrator/master.adoc | 4 +- 13 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 assemblies/assembly-building-and-deploying-serverless-workflows.adoc create mode 100644 modules/orchestrator/con-benefits-of-workflow-images.adoc create mode 100644 modules/orchestrator/con-build-sh-script-and-its-uses.adoc create mode 100644 modules/orchestrator/con-environment-variables-supported-by-the-build-script.adoc create mode 100644 modules/orchestrator/con-generated-workflow-manifests.adoc create mode 100644 modules/orchestrator/con-project-structure-overview.adoc create mode 100644 modules/orchestrator/con-required-tools.adoc create mode 100644 modules/orchestrator/proc-building-locally.adoc create mode 100644 modules/orchestrator/proc-building-the-01-basic-workflow.adoc create mode 100644 modules/orchestrator/proc-creating-and-running-workflows.adoc create mode 100644 modules/orchestrator/proc-deploying-workflows-on-a-cluster.adoc diff --git a/artifacts/attributes.adoc b/artifacts/attributes.adoc index 0af7875790..7aea302480 100644 --- a/artifacts/attributes.adoc +++ b/artifacts/attributes.adoc @@ -151,6 +151,8 @@ :observability-category-link: {product-docs-link}/#Observability :ocp-docs-link: link:https://docs.redhat.com/en/documentation/openshift_container_platform/{ocp-version} :odf-docs-link: link:https://docs.redhat.com/en/documentation/red_hat_openshift_data_foundation/{ocp-version} +:orchestrator-book-link: {product-docs-link}/html-single/orchestrator_in_red_hat_developer_hub/index +:orchestrator-book-title: Orchestrator in {product} :osd-docs-link: link:https://docs.redhat.com/en/documentation/openshift_dedicated/{osd-version} :release-notes-book-link: {product-docs-link}/html-single/red_hat_developer_hub_release_notes/index :release-notes-book-title: {product} release notes diff --git a/assemblies/assembly-building-and-deploying-serverless-workflows.adoc b/assemblies/assembly-building-and-deploying-serverless-workflows.adoc new file mode 100644 index 0000000000..619b4b8481 --- /dev/null +++ b/assemblies/assembly-building-and-deploying-serverless-workflows.adoc @@ -0,0 +1,45 @@ +:_mod-docs-content-type: ASSEMBLY + +ifdef::context[] +[id="assembly-building-and-deploying-serverless-workflows"] +endif::[] +:context: orchestrator-rhdh += Build and deploy serverless workflows + +To deploy a workflow and make it available in the Orchestrator plugin, follow these main steps: + +* Building workflow images +* Generating workflow manifests +* Deploying workflows to a cluster + +This process moves the workflow from your local machine to deployment on a cluster. + +// why build workflow images +include::modules/orchestrator/con-benefits-of-workflow-images.adoc[leveloffset=+1] + +// project structure +include::modules/orchestrator/con-project-structure-overview.adoc[leveloffset=+2] + +// creating and running workflows locally +include::modules/orchestrator/proc-creating-and-running-workflows.adoc[leveloffset=+2] + +// building locally and generating artifacts +include::modules/orchestrator/proc-building-locally.adoc[leveloffset=+1] + +// the script and its uses +include::modules/orchestrator/con-build-sh-script-and-its-uses.adoc[leveloffset=+2] + +// environment variables supported by the script +include::modules/orchestrator/con-environment-variables-supported-by-the-build-script.adoc[leveloffset=+2] + +// required tools +include::modules/orchestrator/con-required-tools.adoc[leveloffset=+2] + +// building the 01_basic workflow +include::modules/orchestrator/proc-building-the-01-basic-workflow.adoc[leveloffset=+2] + +// generated workflow manifests +include::modules/orchestrator/con-generated-workflow-manifests.adoc[leveloffset=+1] + +// deploy workflows on a cluster +include::modules/orchestrator/proc-deploying-workflows-on-a-cluster.adoc[leveloffset=+1] \ No newline at end of file diff --git a/modules/orchestrator/con-benefits-of-workflow-images.adoc b/modules/orchestrator/con-benefits-of-workflow-images.adoc new file mode 100644 index 0000000000..743c7e31ed --- /dev/null +++ b/modules/orchestrator/con-benefits-of-workflow-images.adoc @@ -0,0 +1,11 @@ +:_mod-docs-content-type: CONCEPT + +[id="con-benefits-of-workflow-images.adoc_{context}"] += Benefits of workflow images + +While the OpenShift Serverless Logic Operator supports the building of workflows dynamically, this approach is primarily for experimentation. +For production deployments, building images is the preferred method due to the following reasons: + +* Production readiness: Prebuilt images can be scanned, secured, and tested before going live. +* GitOps compatibility: The Orchestrator relies on a central OpenShift Serverless Logic Operator instance to track workflows and their state. To use this tracking service, you must deploy workflows with the `gitops` profile, which expects a prebuilt image. +* Testing and quality: Building an image gives you more control over the testing process. \ No newline at end of file diff --git a/modules/orchestrator/con-build-sh-script-and-its-uses.adoc b/modules/orchestrator/con-build-sh-script-and-its-uses.adoc new file mode 100644 index 0000000000..45ce77b4e3 --- /dev/null +++ b/modules/orchestrator/con-build-sh-script-and-its-uses.adoc @@ -0,0 +1,35 @@ +:_mod-docs-content-type: CONCEPT + +[id="con-build-sh-script-and-its-uses.adoc_{context}"] += The `build-sh` script functionality and important flags + +The `build-sh` script does the following tasks in order: + +* Generates workflow manifests using the `kn-workflow` CLI. +* Builds the workflow image using `podman` or `docker`. +* Optional: The script pushes the images to an image registry and deploys the workflow using `kubectl`. + +You can review the script configuration options and see available flags and their functions by accessing the help menu: + +[source,bash] +---- +./scripts/build.sh [flags] +---- + +The following flags are essential for running the script: + +[cols="1,3", options="header"] +|=== +|Flag |Description +|`-i`, `--image` |Required: Full image path, for example, `quay.io/orchestrator/demo:latest` +|`-w`, `--workflow-directory` |Workflow source directory (default is the current directory) +|`-m`, `--manifests-directory` |Where to save generated manifests +|`--push` |Push the image to the registry +|`--deploy` |Deploy the workflow +|`-h`, `--help` |Show the help message +|=== + +[TIP] +==== +The script also supports builder and runtime image overrides, namespace targeting, and persistence flags. +==== \ No newline at end of file diff --git a/modules/orchestrator/con-environment-variables-supported-by-the-build-script.adoc b/modules/orchestrator/con-environment-variables-supported-by-the-build-script.adoc new file mode 100644 index 0000000000..d114fc18ab --- /dev/null +++ b/modules/orchestrator/con-environment-variables-supported-by-the-build-script.adoc @@ -0,0 +1,28 @@ +:_mod-docs-content-type: CONCEPT + +[id="con-environment-variables-supported-by-the-build-script.adoc_{context}"] += Environment variables supported by the build script + +The `build-sh` script supports the following environment variables to customize the workflow build process without modifying the script itself: + +`QUARKUS_EXTENSIONS`:: + +The `QUARKUS_EXTENSIONS` variable specifies additional link:https://quarkus.io/extensions/[Quarkus] extensions required by the workflow. This variable takes the format of a comma-separated list of fully qualified extension IDs as shown in the following example: ++ +[source,yaml] +---- +export QUARKUS_EXTENSIONS="io.quarkus:quarkus-smallrye-reactive-messaging-kafka" +---- ++ +Add Kafka messaging support or other integrations at build time. + +`MAVEN_ARGS_APPEND`:: + +The `MAVEN_ARGS_APPEND` variable appends additional arguments to the *Maven build* command. This variable takes the format of a string of Maven CLI arguments as shown in the following example: ++ +[source,yaml] +---- +export MAVEN_ARGS_APPEND="-DmaxYamlCodePoints=35000000" +---- ++ +Control build behavior. For example, set `maxYamlCodePoints` parameter that controls the maximum input size for YAML input files to 35000000 characters (~33MB in UTF-8). \ No newline at end of file diff --git a/modules/orchestrator/con-generated-workflow-manifests.adoc b/modules/orchestrator/con-generated-workflow-manifests.adoc new file mode 100644 index 0000000000..5315700804 --- /dev/null +++ b/modules/orchestrator/con-generated-workflow-manifests.adoc @@ -0,0 +1,76 @@ +:_mod-docs-content-type: CONCEPT + +[id="con-generated-workflow-manifests.adoc_{context}"] += Generated workflow manifests + +The following example is an illustration of what is generated under the `01_basic/manifests`: + +[source,yaml] +---- +01_basic/manifests +├── 00-secret_basic-secrets.yaml +├── 01-configmap_basic-props.yaml +├── 02-configmap_01-basic-resources-schemas.yaml +└── 03-sonataflow_basic.yaml +---- + +`00-secret_basic-secrets.yaml`:: +Contains secrets from `01_basic/src/main/resources/secret.properties`. +Values are not required at this stage as you can set them later after applying CRs or when using GitOps. + +[Important] +==== +In OpenShift Serverless Logic `v1.36`, after updating a secret, you must manually restart the workflow Pod for changes to apply. +==== + +`01-configmap_basic-props.yaml`:: +Holds application properties from application.properties. +Any change to this ConfigMap triggers an automatic Pod restart. + +`02-configmap_01-basic-resources-schemas.yaml`:: +Contains JSON schemas from src/main/resources/schemas. ++ +[NOTE] +==== +You do not need to deploy certain configuration resources when using the GitOps profile. +==== + +`03-sonataflow_basic.yaml`:: +The SonataFlow custom resource (CR) that defines the workflow. ++ +[source,yaml] +---- +podTemplate: + container: + image: quay.io/orchestrator/demo-basic + resources: {} + envFrom: + - secretRef: + name: basic-secrets +---- ++ +[source,yaml,subs="+quotes"] +---- +persistence: + postgresql: + secretRef: + name: `sonataflow-psql-postgresql` + userKey: `____` + passwordKey: `____` + serviceRef: + name: `sonataflow-psql-postgresql` + port: 5432 + databaseName: sonataflow + databaseSchema: basic +---- ++ +where: + +`postgresql:secretRef:name`:: Enter the Secret name for your deployment. +`postgresql:secretRef:userKey`:: Enter the key for your deployment. +`postgresql:secretRef:passwordKey`:: Enter the password for your deployment. +`postgresql:serviceRef:name`:: Enter the Service name for your deployment. ++ +If you must connect to an external database, replace `serviceRef` with `jdbcUrl`. See link:https://docs.redhat.com/en/documentation/red_hat_openshift_serverless/1.36/html-single/serverless_logic/index#serverless-logic-managing-persistence[Managing workflow persistence]. + +By default, the script generates all the manifests without a namespace. You can specify a namespace to the script by using the `--namespace` flag if you know the target namespace in advance. Otherwise, you must provide the namespace when applying the manifests to the cluster. See link:https://docs.redhat.com/en/documentation/red_hat_openshift_serverless/1.36/html-single/serverless_logic/index#serverless-logic-configuring-workflow-services[Configuring workflow services]. \ No newline at end of file diff --git a/modules/orchestrator/con-project-structure-overview.adoc b/modules/orchestrator/con-project-structure-overview.adoc new file mode 100644 index 0000000000..fb83bbf427 --- /dev/null +++ b/modules/orchestrator/con-project-structure-overview.adoc @@ -0,0 +1,32 @@ +:_mod-docs-content-type: CONCEPT + +[id="con-project-structure-overview.adoc_{context}"] += Project structure overview + +The project utilizes *Quarkus project layout* (Maven project structure). This structure is illustrated by the following `01_basic` workflow example: + +[source, yaml] +---- +01_basic +├── pom.xml +├── README.md +└── src + └── main + ├── docker + │ ├── Dockerfile.jvm + │ ├── Dockerfile.legacy-jar + │ ├── Dockerfile.native + │ └── Dockerfile.native-micro + └── resources + ├── application.properties + ├── basic.svg + ├── basic.sw.yaml + ├── schemas + │ ├── basic__main-schema.json + │ └── workflow-output-schema.json + └── secret.properties +---- + +The main workflow resources are located under the `src/main/resources/` directory. + +The `kn-workflow CLI` generated this project structure. You can try generating the structure yourself by following the _Getting Started guide_. For more information on the Quarkus project, see link:https://quarkus.io/guides/getting-started[Creating your first application]. \ No newline at end of file diff --git a/modules/orchestrator/con-required-tools.adoc b/modules/orchestrator/con-required-tools.adoc new file mode 100644 index 0000000000..4c1081b31b --- /dev/null +++ b/modules/orchestrator/con-required-tools.adoc @@ -0,0 +1,17 @@ +:_mod-docs-content-type: CONCEPT + +[id="con-required-tools.adoc_{context}"] += Required tools + +To run the `build-sh` script locally and manage the workflow lifecycle, you must install the following command-line tools: + +[cols="1,3", options="header"] +|=== +|Tool |Conceptual Purpose. +|podman or docker |Container runtime required for building the workflow images. +|`kubectl` |Kubernetes CLI. +|`yq` |YAML processor. +|`jq` |JSON processor. +|`curl`, `git`, `find`, `which`| Shell utilities. +|`kn-workflow` |CLI for generating workflow manifests. +|=== \ No newline at end of file diff --git a/modules/orchestrator/proc-building-locally.adoc b/modules/orchestrator/proc-building-locally.adoc new file mode 100644 index 0000000000..4ac66621d1 --- /dev/null +++ b/modules/orchestrator/proc-building-locally.adoc @@ -0,0 +1,35 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-building-locally.adoc_{context}"] += Building workflow images locally + +You can use the build script (`build.sh`) to build workflow images. You can run it either locally or inside a container. This section highlights how build workflow images locally. + +.Procedure + +. Clone the project as shown in the following example: ++ +[source, yaml] +---- +git clone git@github.com:rhdhorchestrator/orchestrator-demo.git +cd orchestrator-demo +---- + +. Check the help menu of the script: ++ +[source,yaml] +---- +./scripts/build.sh --help +---- + +. Run the `build.sh` script, providing the required flags, for instance, the image path (`-i`), workflow source directory (`-w`), and manifests output directory (`-m`). + ++ +[IMPORTANT] +==== +You must specify the full target image path with a tag as shown in the following example: +[source,yaml] +---- +./scripts/build.sh --image=quay.io/orchestrator/demo-basic:test -w 01_basic/ -m 01_basic/manifests +---- +==== \ No newline at end of file diff --git a/modules/orchestrator/proc-building-the-01-basic-workflow.adoc b/modules/orchestrator/proc-building-the-01-basic-workflow.adoc new file mode 100644 index 0000000000..6dad50f109 --- /dev/null +++ b/modules/orchestrator/proc-building-the-01-basic-workflow.adoc @@ -0,0 +1,27 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-building-the-01-basic-workflow.adoc_{context}"] += Building the `01_basic` workflow + +To run the script from the root directory of the repository, you must use the `-w` flag to point to the workflow directory. Additionally, specify the output directory with the `-m` flag. + +.Prerequisites + +* You have specified the target image using a tag. + +.Procedure + +. Run the following command: ++ +[source,bash] +---- +./scripts/build.sh --image=quay.io/orchestrator/demo-basic:test -w 01_basic/ -m 01_basic/manifests +---- ++ +This build command produces the following two artifacts: + +* A workflow image and Kubernetes manifests: `quay.io/orchestrator/demo-basic:test` and tagged as `latest`. +* Kubernetes manifests under: `01_basic/manifests/` + +. Optional: You can add the `--push` flag to automatically push the image after building. Otherwise, pushing manually is mandatory before deploying. + diff --git a/modules/orchestrator/proc-creating-and-running-workflows.adoc b/modules/orchestrator/proc-creating-and-running-workflows.adoc new file mode 100644 index 0000000000..f470822d8c --- /dev/null +++ b/modules/orchestrator/proc-creating-and-running-workflows.adoc @@ -0,0 +1,43 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-creating-and-running-workflows.adoc_{context}"] += Creating and running your serverless workflow project locally + +The `kn-workflow` CLI is an essential tool that generates workflow manifests and project structures. To ensure successful development and immediate testing, begin developing a new serverless workflow locally by completing the following steps: + +.Procedure +. Use the `kn-workflow` CLI to create a new workflow project, which adheres to the Quarkus structure as shown in the following example: ++ +[source,bash] +---- +kn-workflow quarkus create --name +---- + +. Edit the workflow, add schema and specific files, and run it locally from project folder as shown in the following example: ++ +[source,bash] +---- +kn-workflow quarkus run +---- +. Run the workflow locally using the `kn-workflow run` which pulls the following image: ++ +[source,yaml] +---- +registry.redhat.io/openshift-serverless-1/logic-swf-devmode-rhel8:1.36.0 +---- + +. For building the workflow image, the `kn-workflow` CLI pulls the following images: ++ +[source,yaml] +---- +registry.redhat.io/openshift-serverless-1/logic-swf-builder-rhel8:1.36.0-8 +registry.access.redhat.com/ubi9/openjdk-17:1.21-2 +---- + +[role="_additional-resources"] +.Additional resources + +* link:https://openshift-knative.github.io/docs/docs/latest/serverless-logic/about.html[About OpenShift Serverless Logic] +* link:https://redhat-scholars.github.io/serverless-workflow/osl/index.html[OpenShift Serverless Logic Tutorial] +* {configuring-book-link}#running-behind-a-proxy[Running {product} behind a corporate proxy] +* link:https://docs.redhat.com/en/documentation/red_hat_build_of_quarkus/3.15/html-single/getting_started_with_red_hat_build_of_quarkus/index#proc_online-maven_quarkus-getting-started[Using the Red Hat-hosted Quarkus repository] \ No newline at end of file diff --git a/modules/orchestrator/proc-deploying-workflows-on-a-cluster.adoc b/modules/orchestrator/proc-deploying-workflows-on-a-cluster.adoc new file mode 100644 index 0000000000..29cbcf7b11 --- /dev/null +++ b/modules/orchestrator/proc-deploying-workflows-on-a-cluster.adoc @@ -0,0 +1,108 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-deploying-workflows-on-a-cluster.adoc_{context}"] += Deploying workflows on a cluster + +You can deploy the workflow on a cluster, since the image is pushed to the image registry and the deployment manifests are available. + +.Prerequisites + +* You have an {ocp-short} cluster with the following versions of components installed: + +** {product} ({product-very-short}) `v1.7` +** Orchestrator plugins `v1.7.1` +** OpenShift Serverless `v1.36` +** OpenShift Serverless Logic `v1.36` ++ +For instructions on how to install these components, see the {orchestrator-book-link}#con-orchestrator-plugin-components.adoc_orchestrator-rhdh[Orchestrator plugin components on {ocp-short}]. + +* You must apply the workflow manifests in a namespace that contains a `SonataflowPlatform` custom resource (CR), which manages the link:https://docs.redhat.com/en/documentation/red_hat_openshift_serverless/1.36/html-single/serverless_logic/index#serverless-logic-configuring-workflow-services[supporting services]. + +.Procedure + +. Use the `kubectl create` command specifying the target namespace to apply the Kubernetes manifests as shown in the following example: ++ +[source,bash] +---- +kubectl create -n -f ./01_basic/manifests/. +---- + +. After deployment, monitor the status of the workflow pods as shown in the following example: ++ +[source,yaml] +---- +kubectl get pods -n -l app=basic +---- ++ +The pod may initially appear in an `Error` state because of missing or incomplete configuration in the Secret or ConfigMap. + +. Inspect the Pod logs as shown in the following example: ++ +[source,yaml] +---- +oc logs -n basic-f7c6ff455-vwl56 +---- ++ +The following code is an example of the output: ++ +[source,yaml] +---- +SRCFG00040: The config property quarkus.openapi-generator.notifications.auth.BearerToken.bearer-token is defined as the empty String ("") which the following Converter considered to be null: io.smallrye.config.Converters$BuiltInConverter +java.lang.RuntimeException: Failed to start quarkus +... +Caused by: io.quarkus.runtime.configuration.ConfigurationException: Failed to read configuration properties +---- ++ +The error indicates a missing property: `quarkus.openapi-generator.notifications.auth.BearerToken.bearer-token`. + +. In such a case where the logs show the `ConfigurationException: Failed to read configuration properties` error or indicate a missing value, retrieve the ConfigMap as shown in the following example: ++ +[source,yaml] +---- +oc get -n configmaps basic-props -o yaml +---- ++ +The following code is an example of the sample output: ++ +[source,yaml] +---- +apiVersion: v1 +data: + application.properties: | + # Backstage notifications service + quarkus.rest-client.notifications.url=${BACKSTAGE_NOTIFICATIONS_URL} + quarkus.openapi-generator.notifications.auth.BearerToken.bearer-token=${NOTIFICATIONS_BEARER_TOKEN} +... +---- ++ +Resolve the placeholders using values provided using a Secret. + +. You must edit the corresponding Secret and provide appropriate base64-encoded values to resolve the placeholders in `application.properties` as shown in the following example: ++ +[source,yaml] +---- +kubectl edit secrets -n basic-secrets +---- +. Restart the workflow Pod for Secret changes to take effect in OpenShift Serverless Logic `v1.36`. + +.Verification + +. Verify the deployment status by checking the Pods again as shown in the following example: ++ +[source,yaml] +---- +oc get pods -n -l app=basic +---- ++ +The expected status for a successfully deployed workflow Pod is as shown in the following example: ++ +[source,yaml] +---- +NAME READY STATUS RESTARTS AGE +basic-f7c6ff455-grkxd 1/1 Running 0 47s +---- + +. Once the Pod is in the `Running` state, the workflow now appears in the Orchestrator plugin inside the {product}. + +.Next steps +* Inspect the provided build script to extract the actual steps and implement them in your preferred CI/CD tool, for example, GitHub Actions, GitLab CI, Jenkins, and Tekton. \ No newline at end of file diff --git a/titles/orchestrator/master.adoc b/titles/orchestrator/master.adoc index 97584be038..f483c3021e 100644 --- a/titles/orchestrator/master.adoc +++ b/titles/orchestrator/master.adoc @@ -8,4 +8,6 @@ include::artifacts/attributes.adoc[] include::assemblies/assembly-orchestrator-rhdh.adoc[leveloffset=+1] -include::assemblies/assembly-install-rhdh-orchestrator.adoc[leveloffset=+1] \ No newline at end of file +include::assemblies/assembly-building-and-deploying-serverless-workflows.adoc[leveloffset=+1] + +include::assemblies/assembly-install-rhdh-orchestrator.adoc[leveloffset=+1] From df882142037ab12ef3630e274ea0070c09be7bd5 Mon Sep 17 00:00:00 2001 From: Nick Boldt Date: Fri, 10 Oct 2025 03:51:30 -0300 Subject: [PATCH 19/22] chore(deprecation): move OCM into its own deprecated file (RHIDP-9188) (#1439) --- ...embly-rhdh-installing-dynamic-plugins.adoc | 4 + .../ref-community-plugins.adoc | 1 - .../ref-deprecated-plugins.adoc | 34 ++++++ .../ref-deprecated-plugins.template.adoc | 21 ++++ .../ref-rh-supported-plugins.adoc | 73 +++++------- .../ref-rh-tech-preview-plugins.adoc | 110 +++++++++--------- .../rhdh-supported-plugins.csv | 16 +-- .../dynamic-plugins/rhdh-supported-plugins.sh | 29 ++++- 8 files changed, 178 insertions(+), 110 deletions(-) create mode 100644 modules/dynamic-plugins/ref-deprecated-plugins.adoc create mode 100644 modules/dynamic-plugins/ref-deprecated-plugins.template.adoc diff --git a/assemblies/dynamic-plugins/assembly-rhdh-installing-dynamic-plugins.adoc b/assemblies/dynamic-plugins/assembly-rhdh-installing-dynamic-plugins.adoc index 45759afa0e..d6f4ca3549 100644 --- a/assemblies/dynamic-plugins/assembly-rhdh-installing-dynamic-plugins.adoc +++ b/assemblies/dynamic-plugins/assembly-rhdh-installing-dynamic-plugins.adoc @@ -38,6 +38,10 @@ include::../modules/dynamic-plugins/ref-rh-tech-preview-plugins.adoc[leveloffset [id="rhdh-community-plugins"] include::../modules/dynamic-plugins/ref-community-plugins.adoc[leveloffset=+4] +// Deprecated plugins +[id="rhdh-deprecated-plugins"] +include::../modules/dynamic-plugins/ref-deprecated-plugins.adoc[leveloffset=+4] + // Red Hat compatible plugins [id="rhdh-compatible-plugins"] include::../modules/dynamic-plugins/ref-rh-compatible-plugins.adoc[leveloffset=+1] diff --git a/modules/dynamic-plugins/ref-community-plugins.adoc b/modules/dynamic-plugins/ref-community-plugins.adoc index 81e47f702b..8b13789179 100644 --- a/modules/dynamic-plugins/ref-community-plugins.adoc +++ b/modules/dynamic-plugins/ref-community-plugins.adoc @@ -1,2 +1 @@ -:_mod-docs-content-type: REFERENCE diff --git a/modules/dynamic-plugins/ref-deprecated-plugins.adoc b/modules/dynamic-plugins/ref-deprecated-plugins.adoc new file mode 100644 index 0000000000..81b9d70860 --- /dev/null +++ b/modules/dynamic-plugins/ref-deprecated-plugins.adoc @@ -0,0 +1,34 @@ +:_mod-docs-content-type: REFERENCE + +// This page is generated! Do not edit the .adoc file, but instead run rhdh-supported-plugins.sh to regen this page from the latest plugin metadata. +// cd /path/to/rhdh-documentation; ./modules/dynamic-plugins/rhdh-supported-plugins.sh; ./build/scripts/build.sh; google-chrome titles-generated/main/plugin-rhdh/index.html + +[id="deprecated-plugins"] += Deprecated plugins + +[IMPORTANT] +==== +{product} ({product-very-short}) includes a number of deprecated plugins, which are no longer being actively developed. It is recommended that if you depend on any of these plugins, you migrate to an alternative solution as soon as possible, as these plugins will be removed in a future release. +==== + +{product-very-short} includes the following 2 deprecated plugins: + +[%header,cols=4*] +|=== +|*Name* |*Plugin* |*Version* |*Path and required variables* +|OCM |`https://npmjs.com/package/@backstage-community/plugin-ocm/v/5.6.0[@backstage-community/plugin-ocm]` |5.6.0 +|`./dynamic-plugins/dist/backstage-community-plugin-ocm` + + +|OCM |`https://npmjs.com/package/@backstage-community/plugin-ocm-backend/v/5.7.0[@backstage-community/plugin-ocm-backend]` |5.7.0 +|`./dynamic-plugins/dist/backstage-community-plugin-ocm-backend-dynamic` + +`OCM_HUB_NAME` + +`OCM_HUB_URL` + +`OCM_SA_TOKEN` + + +|=== + diff --git a/modules/dynamic-plugins/ref-deprecated-plugins.template.adoc b/modules/dynamic-plugins/ref-deprecated-plugins.template.adoc new file mode 100644 index 0000000000..8fe3afd38f --- /dev/null +++ b/modules/dynamic-plugins/ref-deprecated-plugins.template.adoc @@ -0,0 +1,21 @@ +:_mod-docs-content-type: REFERENCE + +// This page is generated! Do not edit the .adoc file, but instead run rhdh-supported-plugins.sh to regen this page from the latest plugin metadata. +// cd /path/to/rhdh-documentation; ./modules/dynamic-plugins/rhdh-supported-plugins.sh; ./build/scripts/build.sh; google-chrome titles-generated/main/plugin-rhdh/index.html + +[id="deprecated-plugins"] += Deprecated plugins + +[IMPORTANT] +==== +{product} ({product-very-short}) includes a number of deprecated plugins, which are no longer being actively developed. It is recommended that if you depend on any of these plugins, you migrate to an alternative solution as soon as possible, as these plugins will be removed in a future release. +==== + +{product-very-short} includes the following %%COUNT_4%% deprecated plugins: + +[%header,cols=4*] +|=== +|*Name* |*Plugin* |*Version* |*Path and required variables* +%%TABLE_CONTENT_4%% +|=== + diff --git a/modules/dynamic-plugins/ref-rh-supported-plugins.adoc b/modules/dynamic-plugins/ref-rh-supported-plugins.adoc index 1b40f4e8ea..3cf9f809f1 100644 --- a/modules/dynamic-plugins/ref-rh-supported-plugins.adoc +++ b/modules/dynamic-plugins/ref-rh-supported-plugins.adoc @@ -3,22 +3,27 @@ // This page is generated! Do not edit the .adoc file, but instead run rhdh-supported-plugins.sh to regen this page from the latest plugin metadata. // cd /path/to/rhdh-documentation; ./modules/dynamic-plugins/rhdh-supported-plugins.sh; ./build/scripts/build.sh; google-chrome titles-generated/main/plugin-rhdh/index.html +[id="red-hat-supported-plugins"] = {company-name} supported plugins -{company-name} supports the following 28 plugins: +{company-name} supports the following 26 plugins: [%header,cols=4*] |=== |*Name* |*Plugin* |*Version* |*Path and required variables* -|Analytics Provider Segment |`https://npmjs.com/package/@backstage-community/plugin-analytics-provider-segment/v/1.16.0[@backstage-community/plugin-analytics-provider-segment]` |1.16.0 +|Analytics Provider Segment |`https://npmjs.com/package/@backstage-community/plugin-analytics-provider-segment/v/1.17.0[@backstage-community/plugin-analytics-provider-segment]` |1.17.0 |`./dynamic-plugins/dist/backstage-community-plugin-analytics-provider-segment` +`BACKSTAGE_VERSION` + +`RHDH_VERSION` + `SEGMENT_TEST_MODE` `SEGMENT_WRITE_KEY` -|Keycloak |`https://npmjs.com/package/@backstage-community/plugin-catalog-backend-module-keycloak/v/3.12.1[@backstage-community/plugin-catalog-backend-module-keycloak]` |3.12.1 +|Keycloak |`https://npmjs.com/package/@backstage-community/plugin-catalog-backend-module-keycloak/v/3.12.1[@backstage-community/plugin-catalog-backend-module-keycloak]` |3.12.1 |`./dynamic-plugins/dist/backstage-community-plugin-catalog-backend-module-keycloak-dynamic` `KEYCLOAK_BASE_URL` @@ -32,55 +37,41 @@ `KEYCLOAK_REALM` -|OCM |`https://npmjs.com/package/@backstage-community/plugin-ocm/v/5.6.0[@backstage-community/plugin-ocm]` |5.6.0 -|`./dynamic-plugins/dist/backstage-community-plugin-ocm` - - -|OCM |`https://npmjs.com/package/@backstage-community/plugin-ocm-backend/v/5.7.0[@backstage-community/plugin-ocm-backend]` |5.7.0 -|`./dynamic-plugins/dist/backstage-community-plugin-ocm-backend-dynamic` - -`OCM_HUB_NAME` - -`OCM_HUB_URL` - -`OCM_SA_TOKEN` - - -|Quay |`https://npmjs.com/package/@backstage-community/plugin-quay/v/1.21.1[@backstage-community/plugin-quay]` |1.21.1 +|Quay |`https://npmjs.com/package/@backstage-community/plugin-quay/v/1.21.1[@backstage-community/plugin-quay]` |1.21.1 |`./dynamic-plugins/dist/backstage-community-plugin-quay` -|RBAC |`https://npmjs.com/package/@backstage-community/plugin-rbac/v/1.42.0[@backstage-community/plugin-rbac]` |1.42.0 +|RBAC |`https://npmjs.com/package/@backstage-community/plugin-rbac/v/1.42.0[@backstage-community/plugin-rbac]` |1.42.0 |`./dynamic-plugins/dist/backstage-community-plugin-rbac` -|Kubernetes |`https://npmjs.com/package/@backstage-community/plugin-scaffolder-backend-module-kubernetes/v/2.8.1[@backstage-community/plugin-scaffolder-backend-module-kubernetes]` |2.8.1 +|Kubernetes |`https://npmjs.com/package/@backstage-community/plugin-scaffolder-backend-module-kubernetes/v/2.8.1[@backstage-community/plugin-scaffolder-backend-module-kubernetes]` |2.8.1 |`./dynamic-plugins/dist/backstage-community-plugin-scaffolder-backend-module-kubernetes-dynamic` -|Quay |`https://npmjs.com/package/@backstage-community/plugin-scaffolder-backend-module-quay/v/2.9.1[@backstage-community/plugin-scaffolder-backend-module-quay]` |2.9.1 +|Quay |`https://npmjs.com/package/@backstage-community/plugin-scaffolder-backend-module-quay/v/2.9.1[@backstage-community/plugin-scaffolder-backend-module-quay]` |2.9.1 |`./dynamic-plugins/dist/backstage-community-plugin-scaffolder-backend-module-quay-dynamic` -|Regex |`https://npmjs.com/package/@backstage-community/plugin-scaffolder-backend-module-regex/v/2.7.0[@backstage-community/plugin-scaffolder-backend-module-regex]` |2.7.0 +|Regex |`https://npmjs.com/package/@backstage-community/plugin-scaffolder-backend-module-regex/v/2.7.0[@backstage-community/plugin-scaffolder-backend-module-regex]` |2.7.0 |`./dynamic-plugins/dist/backstage-community-plugin-scaffolder-backend-module-regex-dynamic` -|Tekton |`https://npmjs.com/package/@backstage-community/plugin-tekton/v/3.26.2[@backstage-community/plugin-tekton]` |3.26.2 +|Tekton |`https://npmjs.com/package/@backstage-community/plugin-tekton/v/3.26.2[@backstage-community/plugin-tekton]` |3.26.2 |`./dynamic-plugins/dist/backstage-community-plugin-tekton` -|Topology |`https://npmjs.com/package/@backstage-community/plugin-topology/v/2.2.2[@backstage-community/plugin-topology]` |2.2.2 +|Topology |`https://npmjs.com/package/@backstage-community/plugin-topology/v/2.2.2[@backstage-community/plugin-topology]` |2.2.2 |`./dynamic-plugins/dist/backstage-community-plugin-topology` -|GitHub |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-github/v/0.9.0[@backstage/plugin-catalog-backend-module-github]` |0.9.0 +|GitHub |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-github/v/0.9.0[@backstage/plugin-catalog-backend-module-github]` |0.9.0 |`./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-github-dynamic` `GITHUB_ORG` -|GitHub Org |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-github-org/v/0.3.10[@backstage/plugin-catalog-backend-module-github-org]` |0.3.10 +|GitHub Org |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-github-org/v/0.3.10[@backstage/plugin-catalog-backend-module-github-org]` |0.3.10 |`./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-github-org-dynamic` `GITHUB_ORG` @@ -88,11 +79,11 @@ `GITHUB_URL` -|Ldap |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-ldap/v/0.11.5[@backstage/plugin-catalog-backend-module-ldap]` |0.11.5 +|Ldap |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-ldap/v/0.11.5[@backstage/plugin-catalog-backend-module-ldap]` |0.11.5 |`./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-ldap-dynamic` -|Kubernetes |`https://npmjs.com/package/@backstage/plugin-kubernetes-backend/v/0.19.6[@backstage/plugin-kubernetes-backend]` |0.19.6 +|Kubernetes |`https://npmjs.com/package/@backstage/plugin-kubernetes-backend/v/0.19.6[@backstage/plugin-kubernetes-backend]` |0.19.6 |`./dynamic-plugins/dist/backstage-plugin-kubernetes-backend-dynamic` `K8S_CLUSTER_NAME` @@ -102,55 +93,55 @@ `K8S_CLUSTER_URL` -|GitHub |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-github/v/0.7.1[@backstage/plugin-scaffolder-backend-module-github]` |0.7.1 +|GitHub |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-github/v/0.7.1[@backstage/plugin-scaffolder-backend-module-github]` |0.7.1 |`./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-github-dynamic` -|Signals |`https://npmjs.com/package/@backstage/plugin-signals-backend/v/0.3.4[@backstage/plugin-signals-backend]` |0.3.4 +|Signals |`https://npmjs.com/package/@backstage/plugin-signals-backend/v/0.3.4[@backstage/plugin-signals-backend]` |0.3.4 |`./dynamic-plugins/dist/backstage-plugin-signals-backend-dynamic` -|TechDocs |`https://npmjs.com/package/@backstage/plugin-techdocs/v/1.12.6[@backstage/plugin-techdocs]` |1.12.6 +|TechDocs |`https://npmjs.com/package/@backstage/plugin-techdocs/v/1.12.6[@backstage/plugin-techdocs]` |1.12.6 |`./dynamic-plugins/dist/backstage-plugin-techdocs` -|TechDocs |`https://npmjs.com/package/@backstage/plugin-techdocs-backend/v/2.0.2[@backstage/plugin-techdocs-backend]` |2.0.2 +|TechDocs |`https://npmjs.com/package/@backstage/plugin-techdocs-backend/v/2.0.2[@backstage/plugin-techdocs-backend]` |2.0.2 |`./dynamic-plugins/dist/backstage-plugin-techdocs-backend-dynamic` -|TechDocs Module Addons Contrib |`https://npmjs.com/package/@backstage/plugin-techdocs-module-addons-contrib/v/1.1.24[@backstage/plugin-techdocs-module-addons-contrib]` |1.1.24 +|TechDocs Module Addons Contrib |`https://npmjs.com/package/@backstage/plugin-techdocs-module-addons-contrib/v/1.1.24[@backstage/plugin-techdocs-module-addons-contrib]` |1.1.24 |`./dynamic-plugins/dist/backstage-plugin-techdocs-module-addons-contrib` -|Dynamic Home Page |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-dynamic-home-page/v/1.5.0[@red-hat-developer-hub/backstage-plugin-dynamic-home-page]` |1.5.0 +|Dynamic Home Page |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-dynamic-home-page/v/1.5.0[@red-hat-developer-hub/backstage-plugin-dynamic-home-page]` |1.5.0 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-dynamic-home-page` -|Global Floating Action Button |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-global-floating-action-button/v/1.2.0[@red-hat-developer-hub/backstage-plugin-global-floating-action-button]` |1.2.0 +|Global Floating Action Button |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-global-floating-action-button/v/1.2.0[@red-hat-developer-hub/backstage-plugin-global-floating-action-button]` |1.2.0 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-global-floating-action-button` -|Global Header |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-global-header/v/1.13.0[@red-hat-developer-hub/backstage-plugin-global-header]` |1.13.0 +|Global Header |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-global-header/v/1.13.0[@red-hat-developer-hub/backstage-plugin-global-header]` |1.13.0 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-global-header` -|Adoption Insights |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-adoption-insights/v/0.2.1[@red-hat-developer-hub/backstage-plugin-adoption-insights]` |0.2.1 +|Adoption Insights |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-adoption-insights/v/0.2.1[@red-hat-developer-hub/backstage-plugin-adoption-insights]` |0.2.1 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-adoption-insights` -|Adoption Insights |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-adoption-insights-backend/v/0.2.1[@red-hat-developer-hub/backstage-plugin-adoption-insights-backend]` |0.2.1 +|Adoption Insights |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-adoption-insights-backend/v/0.2.1[@red-hat-developer-hub/backstage-plugin-adoption-insights-backend]` |0.2.1 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-adoption-insights-backend-dynamic` -|Analytics Module Adoption Insights |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-analytics-module-adoption-insights/v/0.2.0[@red-hat-developer-hub/backstage-plugin-analytics-module-adoption-insights]` |0.2.0 +|Analytics Module Adoption Insights |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-analytics-module-adoption-insights/v/0.2.0[@red-hat-developer-hub/backstage-plugin-analytics-module-adoption-insights]` |0.2.0 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-analytics-module-adoption-insights-dynamic` -|Quickstart |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-quickstart/v/1.1.1[@red-hat-developer-hub/backstage-plugin-quickstart]` |1.1.1 +|Quickstart |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-quickstart/v/1.6.2[@red-hat-developer-hub/backstage-plugin-quickstart]` |1.6.2 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-quickstart` -|Argo CD |`https://npmjs.com/package/@roadiehq/backstage-plugin-argo-cd-backend/v/4.3.1[@roadiehq/backstage-plugin-argo-cd-backend]` |4.3.1 +|Argo CD |`https://npmjs.com/package/@roadiehq/backstage-plugin-argo-cd-backend/v/4.3.1[@roadiehq/backstage-plugin-argo-cd-backend]` |4.3.1 |`./dynamic-plugins/dist/roadiehq-backstage-plugin-argo-cd-backend-dynamic` `ARGOCD_AUTH_TOKEN` diff --git a/modules/dynamic-plugins/ref-rh-tech-preview-plugins.adoc b/modules/dynamic-plugins/ref-rh-tech-preview-plugins.adoc index 0c07651cbe..aaa2bc44d2 100644 --- a/modules/dynamic-plugins/ref-rh-tech-preview-plugins.adoc +++ b/modules/dynamic-plugins/ref-rh-tech-preview-plugins.adoc @@ -2,7 +2,7 @@ // This page is generated! Do not edit the .adoc file, but instead run rhdh-supported-plugins.sh to regen this page from the latest plugin metadata. // cd /path/to/rhdh-documentation; ./modules/dynamic-plugins/rhdh-supported-plugins.sh; ./build/scripts/build.sh; google-chrome titles-generated/main/plugin-rhdh/index.html - +[id="red-hat-technology-preview-plugins"] = {company-name} Technology Preview plugins {company-name} provides Technology Preview support for the following 54 plugins: @@ -10,7 +10,7 @@ [%header,cols=4*] |=== |*Name* |*Plugin* |*Version* |*Path and required variables* -|3scale |`https://npmjs.com/package/@backstage-community/plugin-3scale-backend/v/3.6.1[@backstage-community/plugin-3scale-backend]` |3.6.1 +|3scale |`https://npmjs.com/package/@backstage-community/plugin-3scale-backend/v/3.6.1[@backstage-community/plugin-3scale-backend]` |3.6.1 |`./dynamic-plugins/dist/backstage-community-plugin-3scale-backend-dynamic` `THREESCALE_ACCESS_TOKEN` @@ -18,15 +18,15 @@ `THREESCALE_BASE_URL` -|ACR |`https://npmjs.com/package/@backstage-community/plugin-acr/v/1.15.1[@backstage-community/plugin-acr]` |1.15.1 +|ACR |`https://npmjs.com/package/@backstage-community/plugin-acr/v/1.15.1[@backstage-community/plugin-acr]` |1.15.1 |`./dynamic-plugins/dist/backstage-community-plugin-acr` -|Azure Devops |`https://npmjs.com/package/@backstage-community/plugin-azure-devops/v/0.16.1[@backstage-community/plugin-azure-devops]` |0.16.1 +|Azure Devops |`https://npmjs.com/package/@backstage-community/plugin-azure-devops/v/0.16.1[@backstage-community/plugin-azure-devops]` |0.16.1 |`./dynamic-plugins/dist/backstage-community-plugin-azure-devops` -|Azure Devops |`https://npmjs.com/package/@backstage-community/plugin-azure-devops-backend/v/0.17.1[@backstage-community/plugin-azure-devops-backend]` |0.17.1 +|Azure Devops |`https://npmjs.com/package/@backstage-community/plugin-azure-devops-backend/v/0.17.1[@backstage-community/plugin-azure-devops-backend]` |0.17.1 |`./dynamic-plugins/dist/backstage-community-plugin-azure-devops-backend-dynamic` `AZURE_ORG` @@ -34,31 +34,31 @@ `AZURE_TOKEN` -|Pingidentity |`https://npmjs.com/package/@backstage-community/plugin-catalog-backend-module-pingidentity/v/0.5.0[@backstage-community/plugin-catalog-backend-module-pingidentity]` |0.5.0 +|Pingidentity |`https://npmjs.com/package/@backstage-community/plugin-catalog-backend-module-pingidentity/v/0.5.0[@backstage-community/plugin-catalog-backend-module-pingidentity]` |0.5.0 |`./dynamic-plugins/dist/backstage-community-plugin-catalog-backend-module-pingidentity-dynamic` -|Scaffolder Relation Processor |`https://npmjs.com/package/@backstage-community/plugin-catalog-backend-module-scaffolder-relation-processor/v/2.5.0[@backstage-community/plugin-catalog-backend-module-scaffolder-relation-processor]` |2.5.0 +|Scaffolder Relation Processor |`https://npmjs.com/package/@backstage-community/plugin-catalog-backend-module-scaffolder-relation-processor/v/2.5.0[@backstage-community/plugin-catalog-backend-module-scaffolder-relation-processor]` |2.5.0 |`./dynamic-plugins/dist/backstage-community-plugin-catalog-backend-module-scaffolder-relation-processor-dynamic` -|Dynatrace |`https://npmjs.com/package/@backstage-community/plugin-dynatrace/v/10.6.0[@backstage-community/plugin-dynatrace]` |10.6.0 +|Dynatrace |`https://npmjs.com/package/@backstage-community/plugin-dynatrace/v/10.6.0[@backstage-community/plugin-dynatrace]` |10.6.0 |`./dynamic-plugins/dist/backstage-community-plugin-dynatrace` -|GitHub Actions |`https://npmjs.com/package/@backstage-community/plugin-github-actions/v/0.11.1[@backstage-community/plugin-github-actions]` |0.11.1 +|GitHub Actions |`https://npmjs.com/package/@backstage-community/plugin-github-actions/v/0.11.1[@backstage-community/plugin-github-actions]` |0.11.1 |`./dynamic-plugins/dist/backstage-community-plugin-github-actions` -|GitHub Issues |`https://npmjs.com/package/@backstage-community/plugin-github-issues/v/0.10.0[@backstage-community/plugin-github-issues]` |0.10.0 +|GitHub Issues |`https://npmjs.com/package/@backstage-community/plugin-github-issues/v/0.10.0[@backstage-community/plugin-github-issues]` |0.10.0 |`./dynamic-plugins/dist/backstage-community-plugin-github-issues` -|Jenkins |`https://npmjs.com/package/@backstage-community/plugin-jenkins/v/0.20.0[@backstage-community/plugin-jenkins]` |0.20.0 +|Jenkins |`https://npmjs.com/package/@backstage-community/plugin-jenkins/v/0.20.0[@backstage-community/plugin-jenkins]` |0.20.0 |`./dynamic-plugins/dist/backstage-community-plugin-jenkins` -|Jenkins |`https://npmjs.com/package/@backstage-community/plugin-jenkins-backend/v/0.15.0[@backstage-community/plugin-jenkins-backend]` |0.15.0 +|Jenkins |`https://npmjs.com/package/@backstage-community/plugin-jenkins-backend/v/0.15.0[@backstage-community/plugin-jenkins-backend]` |0.15.0 |`./dynamic-plugins/dist/backstage-community-plugin-jenkins-backend-dynamic` `JENKINS_TOKEN` @@ -68,23 +68,23 @@ `JENKINS_USERNAME` -|JFrog Artifactory |`https://npmjs.com/package/@backstage-community/plugin-jfrog-artifactory/v/1.15.3[@backstage-community/plugin-jfrog-artifactory]` |1.15.3 +|JFrog Artifactory |`https://npmjs.com/package/@backstage-community/plugin-jfrog-artifactory/v/1.15.3[@backstage-community/plugin-jfrog-artifactory]` |1.15.3 |`./dynamic-plugins/dist/backstage-community-plugin-jfrog-artifactory` -|Lighthouse |`https://npmjs.com/package/@backstage-community/plugin-lighthouse/v/0.10.0[@backstage-community/plugin-lighthouse]` |0.10.0 +|Lighthouse |`https://npmjs.com/package/@backstage-community/plugin-lighthouse/v/0.10.0[@backstage-community/plugin-lighthouse]` |0.10.0 |`./dynamic-plugins/dist/backstage-community-plugin-lighthouse` -|Nexus Repository Manager |`https://npmjs.com/package/@backstage-community/plugin-nexus-repository-manager/v/1.14.1[@backstage-community/plugin-nexus-repository-manager]` |1.14.1 +|Nexus Repository Manager |`https://npmjs.com/package/@backstage-community/plugin-nexus-repository-manager/v/1.14.1[@backstage-community/plugin-nexus-repository-manager]` |1.14.1 |`./dynamic-plugins/dist/backstage-community-plugin-nexus-repository-manager` -|Argo CD (Red Hat) |`https://npmjs.com/package/@backstage-community/plugin-redhat-argocd/v/1.21.2[@backstage-community/plugin-redhat-argocd]` |1.21.2 +|Argo CD (Red Hat) |`https://npmjs.com/package/@backstage-community/plugin-redhat-argocd/v/1.25.1[@backstage-community/plugin-redhat-argocd]` |1.25.1 |`./dynamic-plugins/dist/backstage-community-plugin-redhat-argocd` -|ServiceNow |`https://npmjs.com/package/@backstage-community/plugin-scaffolder-backend-module-servicenow/v/2.7.0[@backstage-community/plugin-scaffolder-backend-module-servicenow]` |2.7.0 +|ServiceNow |`https://npmjs.com/package/@backstage-community/plugin-scaffolder-backend-module-servicenow/v/2.7.0[@backstage-community/plugin-scaffolder-backend-module-servicenow]` |2.7.0 |`./dynamic-plugins/dist/backstage-community-plugin-scaffolder-backend-module-servicenow-dynamic` `SERVICENOW_BASE_URL` @@ -94,15 +94,15 @@ `SERVICENOW_USERNAME` -|SonarQube |`https://npmjs.com/package/@backstage-community/plugin-scaffolder-backend-module-sonarqube/v/2.7.1[@backstage-community/plugin-scaffolder-backend-module-sonarqube]` |2.7.1 +|SonarQube |`https://npmjs.com/package/@backstage-community/plugin-scaffolder-backend-module-sonarqube/v/2.7.1[@backstage-community/plugin-scaffolder-backend-module-sonarqube]` |2.7.1 |`./dynamic-plugins/dist/backstage-community-plugin-scaffolder-backend-module-sonarqube-dynamic` -|SonarQube |`https://npmjs.com/package/@backstage-community/plugin-sonarqube/v/0.13.0[@backstage-community/plugin-sonarqube]` |0.13.0 +|SonarQube |`https://npmjs.com/package/@backstage-community/plugin-sonarqube/v/0.13.0[@backstage-community/plugin-sonarqube]` |0.13.0 |`./dynamic-plugins/dist/backstage-community-plugin-sonarqube` -|SonarQube |`https://npmjs.com/package/@backstage-community/plugin-sonarqube-backend/v/0.9.2[@backstage-community/plugin-sonarqube-backend]` |0.9.2 +|SonarQube |`https://npmjs.com/package/@backstage-community/plugin-sonarqube-backend/v/0.9.2[@backstage-community/plugin-sonarqube-backend]` |0.9.2 |`./dynamic-plugins/dist/backstage-community-plugin-sonarqube-backend-dynamic` `SONARQUBE_TOKEN` @@ -110,37 +110,37 @@ `SONARQUBE_URL` -|Tech Radar |`https://npmjs.com/package/@backstage-community/plugin-tech-radar/v/1.7.1[@backstage-community/plugin-tech-radar]` |1.7.1 +|Tech Radar |`https://npmjs.com/package/@backstage-community/plugin-tech-radar/v/1.7.1[@backstage-community/plugin-tech-radar]` |1.7.1 |`./dynamic-plugins/dist/backstage-community-plugin-tech-radar` -|Tech Radar |`https://npmjs.com/package/@backstage-community/plugin-tech-radar-backend/v/1.6.0[@backstage-community/plugin-tech-radar-backend]` |1.6.0 +|Tech Radar |`https://npmjs.com/package/@backstage-community/plugin-tech-radar-backend/v/1.6.0[@backstage-community/plugin-tech-radar-backend]` |1.6.0 |`./dynamic-plugins/dist/backstage-community-plugin-tech-radar-backend-dynamic` `TECH_RADAR_DATA_URL` -|Bitbucket Cloud |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-bitbucket-cloud/v/0.4.8[@backstage/plugin-catalog-backend-module-bitbucket-cloud]` |0.4.8 +|Bitbucket Cloud |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-bitbucket-cloud/v/0.4.8[@backstage/plugin-catalog-backend-module-bitbucket-cloud]` |0.4.8 |`./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-bitbucket-cloud-dynamic` `BITBUCKET_WORKSPACE` -|Bitbucket Server |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-bitbucket-server/v/0.4.1[@backstage/plugin-catalog-backend-module-bitbucket-server]` |0.4.1 +|Bitbucket Server |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-bitbucket-server/v/0.4.1[@backstage/plugin-catalog-backend-module-bitbucket-server]` |0.4.1 |`./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-bitbucket-server-dynamic` `BITBUCKET_HOST` -|GitLab |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-gitlab/v/0.6.6[@backstage/plugin-catalog-backend-module-gitlab]` |0.6.6 +|GitLab |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-gitlab/v/0.6.6[@backstage/plugin-catalog-backend-module-gitlab]` |0.6.6 |`./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-gitlab-dynamic` -|GitLab Org |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-gitlab-org/v/0.2.9[@backstage/plugin-catalog-backend-module-gitlab-org]` |0.2.9 +|GitLab Org |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-gitlab-org/v/0.2.9[@backstage/plugin-catalog-backend-module-gitlab-org]` |0.2.9 |`./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-gitlab-org-dynamic` -|MS Graph |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-msgraph/v/0.7.0[@backstage/plugin-catalog-backend-module-msgraph]` |0.7.0 +|MS Graph |`https://npmjs.com/package/@backstage/plugin-catalog-backend-module-msgraph/v/0.7.0[@backstage/plugin-catalog-backend-module-msgraph]` |0.7.0 |`./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-msgraph-dynamic` `MICROSOFT_CLIENT_ID` @@ -150,19 +150,19 @@ `MICROSOFT_TENANT_ID` -|Kubernetes |`https://npmjs.com/package/@backstage/plugin-kubernetes/v/0.12.7[@backstage/plugin-kubernetes]` |0.12.7 +|Kubernetes |`https://npmjs.com/package/@backstage/plugin-kubernetes/v/0.12.7[@backstage/plugin-kubernetes]` |0.12.7 |`./dynamic-plugins/dist/backstage-plugin-kubernetes` -|Notifications |`https://npmjs.com/package/@backstage/plugin-notifications/v/0.5.5[@backstage/plugin-notifications]` |0.5.5 +|Notifications |`https://npmjs.com/package/@backstage/plugin-notifications/v/0.5.5[@backstage/plugin-notifications]` |0.5.5 |`./dynamic-plugins/dist/backstage-plugin-notifications` -|Notifications |`https://npmjs.com/package/@backstage/plugin-notifications-backend/v/0.5.6[@backstage/plugin-notifications-backend]` |0.5.6 +|Notifications |`https://npmjs.com/package/@backstage/plugin-notifications-backend/v/0.5.6[@backstage/plugin-notifications-backend]` |0.5.6 |`./dynamic-plugins/dist/backstage-plugin-notifications-backend-dynamic` -|Notifications |`https://npmjs.com/package/@backstage/plugin-notifications-backend-module-email/v/0.3.9[@backstage/plugin-notifications-backend-module-email]` |0.3.9 +|Notifications |`https://npmjs.com/package/@backstage/plugin-notifications-backend-module-email/v/0.3.9[@backstage/plugin-notifications-backend-module-email]` |0.3.9 |`./dynamic-plugins/dist/backstage-plugin-notifications-backend-module-email-dynamic` `EMAIL_HOSTNAME` @@ -174,35 +174,35 @@ `EMAIL_USERNAME` -|Azure |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-azure/v/0.2.9[@backstage/plugin-scaffolder-backend-module-azure]` |0.2.9 +|Azure |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-azure/v/0.2.9[@backstage/plugin-scaffolder-backend-module-azure]` |0.2.9 |`./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-azure-dynamic` -|Bitbucket Cloud |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-bitbucket-cloud/v/0.2.9[@backstage/plugin-scaffolder-backend-module-bitbucket-cloud]` |0.2.9 +|Bitbucket Cloud |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-bitbucket-cloud/v/0.2.9[@backstage/plugin-scaffolder-backend-module-bitbucket-cloud]` |0.2.9 |`./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-bitbucket-cloud-dynamic` -|Bitbucket Server |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-bitbucket-server/v/0.2.9[@backstage/plugin-scaffolder-backend-module-bitbucket-server]` |0.2.9 +|Bitbucket Server |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-bitbucket-server/v/0.2.9[@backstage/plugin-scaffolder-backend-module-bitbucket-server]` |0.2.9 |`./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-bitbucket-server-dynamic` -|Gerrit |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-gerrit/v/0.2.9[@backstage/plugin-scaffolder-backend-module-gerrit]` |0.2.9 +|Gerrit |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-gerrit/v/0.2.9[@backstage/plugin-scaffolder-backend-module-gerrit]` |0.2.9 |`./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-gerrit-dynamic` -|GitLab |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-gitlab/v/0.9.1[@backstage/plugin-scaffolder-backend-module-gitlab]` |0.9.1 +|GitLab |`https://npmjs.com/package/@backstage/plugin-scaffolder-backend-module-gitlab/v/0.9.1[@backstage/plugin-scaffolder-backend-module-gitlab]` |0.9.1 |`./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-gitlab-dynamic` -|Signals |`https://npmjs.com/package/@backstage/plugin-signals/v/0.0.19[@backstage/plugin-signals]` |0.0.19 +|Signals |`https://npmjs.com/package/@backstage/plugin-signals/v/0.0.19[@backstage/plugin-signals]` |0.0.19 |`./dynamic-plugins/dist/backstage-plugin-signals` -|GitLab |`https://npmjs.com/package/@immobiliarelabs/backstage-plugin-gitlab/v/6.12.1[@immobiliarelabs/backstage-plugin-gitlab]` |6.12.1 +|GitLab |`https://npmjs.com/package/@immobiliarelabs/backstage-plugin-gitlab/v/6.12.1[@immobiliarelabs/backstage-plugin-gitlab]` |6.12.1 |`./dynamic-plugins/dist/immobiliarelabs-backstage-plugin-gitlab` -|GitLab |`https://npmjs.com/package/@immobiliarelabs/backstage-plugin-gitlab-backend/v/6.12.0[@immobiliarelabs/backstage-plugin-gitlab-backend]` |6.12.0 +|GitLab |`https://npmjs.com/package/@immobiliarelabs/backstage-plugin-gitlab-backend/v/6.12.0[@immobiliarelabs/backstage-plugin-gitlab-backend]` |6.12.0 |`./dynamic-plugins/dist/immobiliarelabs-backstage-plugin-gitlab-backend-dynamic` `GITLAB_HOST` @@ -210,11 +210,11 @@ `GITLAB_TOKEN` -|PagerDuty |`https://npmjs.com/package/@pagerduty/backstage-plugin/v/0.15.5[@pagerduty/backstage-plugin]` |0.15.5 +|PagerDuty |`https://npmjs.com/package/@pagerduty/backstage-plugin/v/0.15.5[@pagerduty/backstage-plugin]` |0.15.5 |`./dynamic-plugins/dist/pagerduty-backstage-plugin` -|PagerDuty |`https://npmjs.com/package/@pagerduty/backstage-plugin-backend/v/0.9.6[@pagerduty/backstage-plugin-backend]` |0.9.6 +|PagerDuty |`https://npmjs.com/package/@pagerduty/backstage-plugin-backend/v/0.9.6[@pagerduty/backstage-plugin-backend]` |0.9.6 |`./dynamic-plugins/dist/pagerduty-backstage-plugin-backend-dynamic` `PAGERDUTY_API_BASE` @@ -226,51 +226,51 @@ `PAGERDUTY_SUBDOMAIN` -|Azure Repositories |`https://npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-repositories/v/0.3.0[@parfuemerie-douglas/scaffolder-backend-module-azure-repositories]` |0.3.0 +|Azure Repositories |`https://npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-repositories/v/0.3.0[@parfuemerie-douglas/scaffolder-backend-module-azure-repositories]` |0.3.0 |`./dynamic-plugins/dist/parfuemerie-douglas-scaffolder-backend-module-azure-repositories-dynamic` -|Bulk Import |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-bulk-import/v/1.13.4[@red-hat-developer-hub/backstage-plugin-bulk-import]` |1.13.4 +|Bulk Import |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-bulk-import/v/1.13.4[@red-hat-developer-hub/backstage-plugin-bulk-import]` |1.13.4 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-bulk-import` -|Bulk Import |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-bulk-import-backend/v/6.1.7[@red-hat-developer-hub/backstage-plugin-bulk-import-backend]` |6.1.7 +|Bulk Import |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-bulk-import-backend/v/6.1.7[@red-hat-developer-hub/backstage-plugin-bulk-import-backend]` |6.1.7 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-bulk-import-backend-dynamic` -|Marketplace |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-catalog-backend-module-marketplace/v/0.4.4[@red-hat-developer-hub/backstage-plugin-catalog-backend-module-marketplace]` |0.4.4 +|Marketplace |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-catalog-backend-module-marketplace/v/0.4.4[@red-hat-developer-hub/backstage-plugin-catalog-backend-module-marketplace]` |0.4.4 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-catalog-backend-module-marketplace-dynamic` -|Marketplace |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-marketplace/v/0.8.5[@red-hat-developer-hub/backstage-plugin-marketplace]` |0.8.5 +|Marketplace |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-marketplace/v/0.8.5[@red-hat-developer-hub/backstage-plugin-marketplace]` |0.8.5 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-marketplace` -|Marketplace |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-marketplace-backend/v/0.7.3[@red-hat-developer-hub/backstage-plugin-marketplace-backend]` |0.7.3 +|Marketplace |`https://npmjs.com/package/@red-hat-developer-hub/backstage-plugin-marketplace-backend/v/0.7.3[@red-hat-developer-hub/backstage-plugin-marketplace-backend]` |0.7.3 |`./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-marketplace-backend-dynamic` -|Datadog |`https://npmjs.com/package/@roadiehq/backstage-plugin-datadog/v/2.4.3[@roadiehq/backstage-plugin-datadog]` |2.4.3 +|Datadog |`https://npmjs.com/package/@roadiehq/backstage-plugin-datadog/v/2.4.3[@roadiehq/backstage-plugin-datadog]` |2.4.3 |`./dynamic-plugins/dist/roadiehq-backstage-plugin-datadog` -|GitHub Insights |`https://npmjs.com/package/@roadiehq/backstage-plugin-github-insights/v/3.1.4[@roadiehq/backstage-plugin-github-insights]` |3.1.4 +|GitHub Insights |`https://npmjs.com/package/@roadiehq/backstage-plugin-github-insights/v/3.1.4[@roadiehq/backstage-plugin-github-insights]` |3.1.4 |`./dynamic-plugins/dist/roadiehq-backstage-plugin-github-insights` -|GitHub Pull Requests |`https://npmjs.com/package/@roadiehq/backstage-plugin-github-pull-requests/v/3.4.2[@roadiehq/backstage-plugin-github-pull-requests]` |3.4.2 +|GitHub Pull Requests |`https://npmjs.com/package/@roadiehq/backstage-plugin-github-pull-requests/v/3.4.2[@roadiehq/backstage-plugin-github-pull-requests]` |3.4.2 |`./dynamic-plugins/dist/roadiehq-backstage-plugin-github-pull-requests` -|Jira |`https://npmjs.com/package/@roadiehq/backstage-plugin-jira/v/2.9.0[@roadiehq/backstage-plugin-jira]` |2.9.0 +|Jira |`https://npmjs.com/package/@roadiehq/backstage-plugin-jira/v/2.9.0[@roadiehq/backstage-plugin-jira]` |2.9.0 |`./dynamic-plugins/dist/roadiehq-backstage-plugin-jira` -|Security Insights |`https://npmjs.com/package/@roadiehq/backstage-plugin-security-insights/v/3.1.3[@roadiehq/backstage-plugin-security-insights]` |3.1.3 +|Security Insights |`https://npmjs.com/package/@roadiehq/backstage-plugin-security-insights/v/3.1.3[@roadiehq/backstage-plugin-security-insights]` |3.1.3 |`./dynamic-plugins/dist/roadiehq-backstage-plugin-security-insights` -|Argo CD |`https://npmjs.com/package/@roadiehq/scaffolder-backend-argocd/v/1.6.0[@roadiehq/scaffolder-backend-argocd]` |1.6.0 +|Argo CD |`https://npmjs.com/package/@roadiehq/scaffolder-backend-argocd/v/1.6.0[@roadiehq/scaffolder-backend-argocd]` |1.6.0 |`./dynamic-plugins/dist/roadiehq-scaffolder-backend-argocd-dynamic` `ARGOCD_AUTH_TOKEN` @@ -286,11 +286,11 @@ `ARGOCD_USERNAME` -|Http Request |`https://npmjs.com/package/@roadiehq/scaffolder-backend-module-http-request/v/5.3.4[@roadiehq/scaffolder-backend-module-http-request]` |5.3.4 +|Http Request |`https://npmjs.com/package/@roadiehq/scaffolder-backend-module-http-request/v/5.3.4[@roadiehq/scaffolder-backend-module-http-request]` |5.3.4 |`./dynamic-plugins/dist/roadiehq-scaffolder-backend-module-http-request-dynamic` -|Utils |`https://npmjs.com/package/@roadiehq/scaffolder-backend-module-utils/v/3.5.0[@roadiehq/scaffolder-backend-module-utils]` |3.5.0 +|Utils |`https://npmjs.com/package/@roadiehq/scaffolder-backend-module-utils/v/3.5.0[@roadiehq/scaffolder-backend-module-utils]` |3.5.0 |`./dynamic-plugins/dist/roadiehq-scaffolder-backend-module-utils-dynamic` diff --git a/modules/dynamic-plugins/rhdh-supported-plugins.csv b/modules/dynamic-plugins/rhdh-supported-plugins.csv index 07fe7ab778..6c3356f5ff 100644 --- a/modules/dynamic-plugins/rhdh-supported-plugins.csv +++ b/modules/dynamic-plugins/rhdh-supported-plugins.csv @@ -2,23 +2,21 @@ "Adoption Insights","@red-hat-developer-hub/backstage-plugin-adoption-insights","Frontend","0.2.1","Production","active","./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-adoption-insights",";","Enabled" "Adoption Insights","@red-hat-developer-hub/backstage-plugin-adoption-insights-backend","Backend","0.2.1","Production","active","./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-adoption-insights-backend-dynamic",";","Enabled" "Analytics Module Adoption Insights","@red-hat-developer-hub/backstage-plugin-analytics-module-adoption-insights","Frontend","0.2.0","Production","active","./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-analytics-module-adoption-insights-dynamic",";","Enabled" -"Analytics Provider Segment","@backstage-community/plugin-analytics-provider-segment","Frontend","1.16.0","Production","active","./dynamic-plugins/dist/backstage-community-plugin-analytics-provider-segment","`SEGMENT_TEST_MODE`;`SEGMENT_WRITE_KEY`;","Enabled" +"Analytics Provider Segment","@backstage-community/plugin-analytics-provider-segment","Frontend","1.17.0","Production","active","./dynamic-plugins/dist/backstage-community-plugin-analytics-provider-segment","`BACKSTAGE_VERSION`;`RHDH_VERSION`;`SEGMENT_TEST_MODE`;`SEGMENT_WRITE_KEY`;","Enabled" "Argo CD","@roadiehq/backstage-plugin-argo-cd-backend","Backend","4.3.1","Production","active","./dynamic-plugins/dist/roadiehq-backstage-plugin-argo-cd-backend-dynamic","`ARGOCD_AUTH_TOKEN`;`ARGOCD_AUTH_TOKEN2`;`ARGOCD_INSTANCE1_URL`;`ARGOCD_INSTANCE2_URL`;`ARGOCD_PASSWORD`;`ARGOCD_USERNAME`;","Disabled" "Dynamic Home Page","@red-hat-developer-hub/backstage-plugin-dynamic-home-page","Frontend","1.5.0","Production","active","./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-dynamic-home-page",";","Enabled" -"GitHub Org","@backstage/plugin-catalog-backend-module-github-org","Backend","0.3.10","Production","active","./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-github-org-dynamic","`GITHUB_ORG`;`GITHUB_URL`;","Disabled" "GitHub","@backstage/plugin-catalog-backend-module-github","Backend","0.9.0","Production","active","./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-github-dynamic","`GITHUB_ORG`;","Disabled" "GitHub","@backstage/plugin-scaffolder-backend-module-github","Backend","0.7.1","Production","active","./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-github-dynamic",";","Disabled" +"GitHub Org","@backstage/plugin-catalog-backend-module-github-org","Backend","0.3.10","Production","active","./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-github-org-dynamic","`GITHUB_ORG`;`GITHUB_URL`;","Disabled" "Global Floating Action Button","@red-hat-developer-hub/backstage-plugin-global-floating-action-button","Frontend","1.2.0","Production","active","./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-global-floating-action-button",";","Enabled" "Global Header","@red-hat-developer-hub/backstage-plugin-global-header","Frontend","1.13.0","Production","active","./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-global-header",";","Enabled" "Keycloak","@backstage-community/plugin-catalog-backend-module-keycloak","Backend","3.12.1","Production","active","./dynamic-plugins/dist/backstage-community-plugin-catalog-backend-module-keycloak-dynamic","`KEYCLOAK_BASE_URL`;`KEYCLOAK_CLIENT_ID`;`KEYCLOAK_CLIENT_SECRET`;`KEYCLOAK_LOGIN_REALM`;`KEYCLOAK_REALM`;","Disabled" "Kubernetes","@backstage/plugin-kubernetes-backend","Backend","0.19.6","Production","active","./dynamic-plugins/dist/backstage-plugin-kubernetes-backend-dynamic","`K8S_CLUSTER_NAME`;`K8S_CLUSTER_TOKEN`;`K8S_CLUSTER_URL`;","Disabled" "Kubernetes","@backstage-community/plugin-scaffolder-backend-module-kubernetes","Backend","2.8.1","Production","active","./dynamic-plugins/dist/backstage-community-plugin-scaffolder-backend-module-kubernetes-dynamic",";","Disabled" "Ldap","@backstage/plugin-catalog-backend-module-ldap","Backend","0.11.5","Production","active","./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-ldap-dynamic",";","Disabled" -"OCM","@backstage-community/plugin-ocm","Frontend","5.6.0","Production","active","./dynamic-plugins/dist/backstage-community-plugin-ocm",";","Disabled" -"OCM","@backstage-community/plugin-ocm-backend","Backend","5.7.0","Production","active","./dynamic-plugins/dist/backstage-community-plugin-ocm-backend-dynamic","`OCM_HUB_NAME`;`OCM_HUB_URL`;`OCM_SA_TOKEN`;","Disabled" "Quay","@backstage-community/plugin-quay","Frontend","1.21.1","Production","active","./dynamic-plugins/dist/backstage-community-plugin-quay",";","Disabled" "Quay","@backstage-community/plugin-scaffolder-backend-module-quay","Backend","2.9.1","Production","active","./dynamic-plugins/dist/backstage-community-plugin-scaffolder-backend-module-quay-dynamic",";","Enabled" -"Quickstart","@red-hat-developer-hub/backstage-plugin-quickstart","Frontend","1.1.1","Production","active","./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-quickstart",";","Enabled" +"Quickstart","@red-hat-developer-hub/backstage-plugin-quickstart","Frontend","1.6.2","Production","active","./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-quickstart",";","Enabled" "RBAC","@backstage-community/plugin-rbac","Frontend","1.42.0","Production","active","./dynamic-plugins/dist/backstage-community-plugin-rbac",";","Disabled" "Regex","@backstage-community/plugin-scaffolder-backend-module-regex","Backend","2.7.0","Production","active","./dynamic-plugins/dist/backstage-community-plugin-scaffolder-backend-module-regex-dynamic",";","Enabled" "Signals","@backstage/plugin-signals-backend","Backend","0.3.4","Production","active","./dynamic-plugins/dist/backstage-plugin-signals-backend-dynamic",";","Disabled" @@ -26,12 +24,12 @@ "Topology","@backstage-community/plugin-topology","Frontend","2.2.2","Production","active","./dynamic-plugins/dist/backstage-community-plugin-topology",";","Disabled" "3scale","@backstage-community/plugin-3scale-backend","Backend","3.6.1","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-community-plugin-3scale-backend-dynamic","`THREESCALE_ACCESS_TOKEN`;`THREESCALE_BASE_URL`;","Disabled" "ACR","@backstage-community/plugin-acr","Frontend","1.15.1","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-community-plugin-acr",";","Disabled" -"Argo CD (Red Hat)","@backstage-community/plugin-redhat-argocd","Frontend","1.21.2","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-community-plugin-redhat-argocd",";","Disabled" "Argo CD","@roadiehq/scaffolder-backend-argocd","Backend","1.6.0","Red Hat Tech Preview","active","./dynamic-plugins/dist/roadiehq-scaffolder-backend-argocd-dynamic","`ARGOCD_AUTH_TOKEN`;`ARGOCD_AUTH_TOKEN2`;`ARGOCD_INSTANCE1_URL`;`ARGOCD_INSTANCE2_URL`;`ARGOCD_PASSWORD`;`ARGOCD_USERNAME`;","Disabled" +"Argo CD (Red Hat)","@backstage-community/plugin-redhat-argocd","Frontend","1.25.1","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-community-plugin-redhat-argocd",";","Disabled" +"Azure","@backstage/plugin-scaffolder-backend-module-azure","Backend","0.2.9","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-azure-dynamic",";","Disabled" "Azure Devops","@backstage-community/plugin-azure-devops","Frontend","0.16.1","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-community-plugin-azure-devops",";","Disabled" "Azure Devops","@backstage-community/plugin-azure-devops-backend","Backend","0.17.1","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-community-plugin-azure-devops-backend-dynamic","`AZURE_ORG`;`AZURE_TOKEN`;","Disabled" "Azure Repositories","@parfuemerie-douglas/scaffolder-backend-module-azure-repositories","Backend","0.3.0","Red Hat Tech Preview","active","./dynamic-plugins/dist/parfuemerie-douglas-scaffolder-backend-module-azure-repositories-dynamic",";","Disabled" -"Azure","@backstage/plugin-scaffolder-backend-module-azure","Backend","0.2.9","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-azure-dynamic",";","Disabled" "Bitbucket Cloud","@backstage/plugin-catalog-backend-module-bitbucket-cloud","Backend","0.4.8","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-bitbucket-cloud-dynamic","`BITBUCKET_WORKSPACE`;","Disabled" "Bitbucket Cloud","@backstage/plugin-scaffolder-backend-module-bitbucket-cloud","Backend","0.2.9","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-bitbucket-cloud-dynamic",";","Disabled" "Bitbucket Server","@backstage/plugin-catalog-backend-module-bitbucket-server","Backend","0.4.1","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-bitbucket-server-dynamic","`BITBUCKET_HOST`;","Disabled" @@ -45,11 +43,11 @@ "GitHub Insights","@roadiehq/backstage-plugin-github-insights","Frontend","3.1.4","Red Hat Tech Preview","active","./dynamic-plugins/dist/roadiehq-backstage-plugin-github-insights",";","Disabled" "GitHub Issues","@backstage-community/plugin-github-issues","Frontend","0.10.0","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-community-plugin-github-issues",";","Disabled" "GitHub Pull Requests","@roadiehq/backstage-plugin-github-pull-requests","Frontend","3.4.2","Red Hat Tech Preview","active","./dynamic-plugins/dist/roadiehq-backstage-plugin-github-pull-requests",";","Disabled" -"GitLab Org","@backstage/plugin-catalog-backend-module-gitlab-org","Backend","0.2.9","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-gitlab-org-dynamic",";","Disabled" "GitLab","@immobiliarelabs/backstage-plugin-gitlab","Frontend","6.12.1","Red Hat Tech Preview","active","./dynamic-plugins/dist/immobiliarelabs-backstage-plugin-gitlab",";","Disabled" "GitLab","@backstage/plugin-catalog-backend-module-gitlab","Backend","0.6.6","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-gitlab-dynamic",";","Disabled" "GitLab","@immobiliarelabs/backstage-plugin-gitlab-backend","Backend","6.12.0","Red Hat Tech Preview","active","./dynamic-plugins/dist/immobiliarelabs-backstage-plugin-gitlab-backend-dynamic","`GITLAB_HOST`;`GITLAB_TOKEN`;","Disabled" "GitLab","@backstage/plugin-scaffolder-backend-module-gitlab","Backend","0.9.1","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-gitlab-dynamic",";","Disabled" +"GitLab Org","@backstage/plugin-catalog-backend-module-gitlab-org","Backend","0.2.9","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-gitlab-org-dynamic",";","Disabled" "Http Request","@roadiehq/scaffolder-backend-module-http-request","Backend","5.3.4","Red Hat Tech Preview","active","./dynamic-plugins/dist/roadiehq-scaffolder-backend-module-http-request-dynamic",";","Disabled" "Jenkins","@backstage-community/plugin-jenkins","Frontend","0.20.0","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-community-plugin-jenkins",";","Disabled" "Jenkins","@backstage-community/plugin-jenkins-backend","Backend","0.15.0","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-community-plugin-jenkins-backend-dynamic","`JENKINS_TOKEN`;`JENKINS_URL`;`JENKINS_USERNAME`;","Disabled" @@ -78,3 +76,5 @@ "Tech Radar","@backstage-community/plugin-tech-radar","Frontend","1.7.1","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-community-plugin-tech-radar",";","Disabled" "Tech Radar","@backstage-community/plugin-tech-radar-backend","Backend","1.6.0","Red Hat Tech Preview","active","./dynamic-plugins/dist/backstage-community-plugin-tech-radar-backend-dynamic","`TECH_RADAR_DATA_URL`;","Disabled" "Utils","@roadiehq/scaffolder-backend-module-utils","Backend","3.5.0","Red Hat Tech Preview","active","./dynamic-plugins/dist/roadiehq-scaffolder-backend-module-utils-dynamic",";","Disabled" +"OCM","@backstage-community/plugin-ocm","Frontend","5.6.0","Production","deprecated","./dynamic-plugins/dist/backstage-community-plugin-ocm",";","Disabled" +"OCM","@backstage-community/plugin-ocm-backend","Backend","5.7.0","Production","deprecated","./dynamic-plugins/dist/backstage-community-plugin-ocm-backend-dynamic","`OCM_HUB_NAME`;`OCM_HUB_URL`;`OCM_SA_TOKEN`;","Disabled" diff --git a/modules/dynamic-plugins/rhdh-supported-plugins.sh b/modules/dynamic-plugins/rhdh-supported-plugins.sh index 31891144c4..2911c03de2 100755 --- a/modules/dynamic-plugins/rhdh-supported-plugins.sh +++ b/modules/dynamic-plugins/rhdh-supported-plugins.sh @@ -327,7 +327,9 @@ for y in $yamls; do csv_content="\"$PrettyName\",\"$Plugin\",\"$Role\",\"$Version\",\"$Support_Level\",\"$Lifecycle\",\"$Path\",\"${Required_Variables_CSV}\",\"$Default\"" # split into three tables based on support level - if [[ ${Support_Level} == "Production" ]]; then + if [[ ${Lifecycle} == "deprecated" ]]; then + echo "$key|$adoc_content" >> "$TEMP_DIR/adoc.deprecated.tmp" + elif [[ ${Support_Level} == "Production" ]]; then echo "$key|$adoc_content" >> "$TEMP_DIR/adoc.production.tmp" elif [[ ${Support_Level} == "Red Hat Tech Preview" ]]; then echo "$key|$adoc_content" >> "$TEMP_DIR/adoc.tech-preview.tmp" @@ -337,7 +339,9 @@ for y in $yamls; do # Group CSV by support level SupportSort=3 - if [[ ${Support_Level} == "Production" ]]; then + if [[ ${Lifecycle} == "deprecated" ]]; then + SupportSort=4 + elif [[ ${Support_Level} == "Production" ]]; then SupportSort=1 elif [[ ${Support_Level} == "Red Hat Tech Preview" ]]; then SupportSort=2 @@ -406,7 +410,22 @@ if [[ -f "$temp_file" ]]; then fi num_plugins+=($count) -# Process CSV: sort by SupportSort (1,2,3) then PrettyName, and omit techdocs +# 3) Deprecated +temp_file="$TEMP_DIR/adoc.deprecated.tmp" +out_file="${0/.sh/.ref-deprecated-plugins}" +rm -f "$out_file" +count=0 +if [[ -f "$temp_file" ]]; then + sort "$temp_file" | while IFS='|' read -r key content; do + (( count = count + 1 )) + if [[ $QUIET -eq 0 ]]; then echo " * [$count] $key [ ${out_file##*/} ]"; fi + echo -e "$content" >> "$out_file" + done + count=$(wc -l < "$temp_file") +fi +num_plugins+=($count) + +# Process CSV: sort by SupportSort (1,2,3,4) then PrettyName, and omit techdocs if [[ -f "$TEMP_DIR/csv.tmp" ]]; then sort -t '|' -k1,1 -k2,2 "$TEMP_DIR/csv.tmp" | while IFS='|' read -r key content; do # RHIDP-4196 omit techdocs plugins from the .csv @@ -420,10 +439,10 @@ fi if [[ $QUIET -eq 0 ]]; then echo; fi -# merge the content from the three .adocX files into the .template.adoc file, replacing the TABLE_CONTENT markers +# merge the content from the 4 .adocX files into the .template.adoc file, replacing the TABLE_CONTENT markers count=1 index=0 -for d in ref-rh-supported-plugins ref-rh-tech-preview-plugins ref-community-plugins; do +for d in ref-rh-supported-plugins ref-rh-tech-preview-plugins ref-community-plugins ref-deprecated-plugins; do (( index = count - 1 )) this_num_plugins=${num_plugins[$index]} echo -n -e "${green}[$count] Processing $d ${norm}..." From c66b93d26791d079d8a8cf6ff1316748f024408e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20Flore-Th=C3=A9bault?= Date: Fri, 10 Oct 2025 09:10:25 +0200 Subject: [PATCH 20/22] RHIDP-9017 Updated authenticating with GitHub (#1429) Co-authored-by: Jessica He Co-authored-by: Priyanka Abel --- ...y-enabling-authentication-with-github.adoc | 11 ++++ .../assembly-enabling-authentication.adoc | 2 +- ...-an-auxiliary-authentication-provider.adoc | 47 ++++++++++++++ ...bling-user-authentication-with-github.adoc | 63 +----------------- ...thentication-with-github-common-steps.adoc | 64 +++++++++++++++++++ ...-enabling-github-repository-discovery.adoc | 2 + 6 files changed, 126 insertions(+), 63 deletions(-) create mode 100644 assemblies/assembly-enabling-authentication-with-github.adoc create mode 100644 modules/authentication/proc-enabling-user-authentication-with-github-as-an-auxiliary-authentication-provider.adoc create mode 100644 modules/authentication/snip-enabling-user-authentication-with-github-common-steps.adoc diff --git a/assemblies/assembly-enabling-authentication-with-github.adoc b/assemblies/assembly-enabling-authentication-with-github.adoc new file mode 100644 index 0000000000..93848481ba --- /dev/null +++ b/assemblies/assembly-enabling-authentication-with-github.adoc @@ -0,0 +1,11 @@ +:_mod-docs-content-type: ASSEMBLY +:optional-steps: enable + +[id='enabling-authentication-with-github'] += Enabling authentication with GitHub + +include::modules/authentication/proc-enabling-user-authentication-with-github.adoc[leveloffset=+1] + + +include::modules/authentication/proc-enabling-user-authentication-with-github-as-an-auxiliary-authentication-provider.adoc[leveloffset=+1] + diff --git a/assemblies/assembly-enabling-authentication.adoc b/assemblies/assembly-enabling-authentication.adoc index 64be70baa2..0dc5223f54 100644 --- a/assemblies/assembly-enabling-authentication.adoc +++ b/assemblies/assembly-enabling-authentication.adoc @@ -13,7 +13,7 @@ include::assembly-authenticating-with-the-guest-user.adoc[leveloffset=+1] include::assembly-authenticating-with-rhbk.adoc[leveloffset=+1] -include::modules/authentication/proc-enabling-user-authentication-with-github.adoc[leveloffset=+1] +include::assembly-enabling-authentication-with-github.adoc[leveloffset=+1] include::modules/authentication/proc-enabling-user-authentication-with-microsoft-azure.adoc[leveloffset=+1] diff --git a/modules/authentication/proc-enabling-user-authentication-with-github-as-an-auxiliary-authentication-provider.adoc b/modules/authentication/proc-enabling-user-authentication-with-github-as-an-auxiliary-authentication-provider.adoc new file mode 100644 index 0000000000..ad26bf65fd --- /dev/null +++ b/modules/authentication/proc-enabling-user-authentication-with-github-as-an-auxiliary-authentication-provider.adoc @@ -0,0 +1,47 @@ +:_mod-docs-content-type: PROCEDURE + +[id="enabling-user-authentication-with-github-as-an-auxiliary-authentication-provider"] += Enabling user authentication with GitHub as an auxiliary authentication provider + +To allow users to access GitHub templates or plugins that require GitHub authentication, configure GitHub as an auxiliary authentication provider. This method relies on a primary authentication provider for user identity management, and skips resolving user identity from this provider. + +.Prerequisites +* You have {configuring-book-link}[added a custom {product-short} application configuration] with another authentication provider enabled, and have enough permissions to change it. + +include::snip-enabling-user-authentication-with-github-common-steps.adoc[] + +. To set up the GitHub authentication provider as an auxiliary authentication provider, add the `auth.providers.github` section to your `{my-app-config-file}` file: ++ +[source,yaml] +---- +auth: + providers: + github: + production: + clientId: ${GITHUB_CLIENT_ID} + clientSecret: ${GITHUB_CLIENT_SECRET} + disableIdentityResolution: true +---- ++ +where: +`clientId`:: +Enter the configured secret variable name: `$\{GITHUB_CLIENT_ID}`. + +`clientSecret`:: +Enter the configured secret variable name: `$\{GITHUB_CLIENT_SECRET}`. + +`disableIdentityResolution`:: +Enter `true`to skip user identity resolution for this provider to enable sign-in from an auxiliary authentication provider. +Do not enable this setting on the primary authentication provider you plan on using for sign-in and identity management. + +.Verification + +. Go to the {product-short} login page. +. Log in with your primary authentication provider account. +. In the top user menu, go to *Settings* > *Authentication Providers*. +. In the *GitHub* line, click the *Sign in* button and log in. +. In the *GitHub* line, the button displays *Sign out*. + +.Additional resources +* {integrating-with-github-book-link}[{integrating-with-github-book-title}] + diff --git a/modules/authentication/proc-enabling-user-authentication-with-github.adoc b/modules/authentication/proc-enabling-user-authentication-with-github.adoc index 4743a717f6..e4f5779469 100644 --- a/modules/authentication/proc-enabling-user-authentication-with-github.adoc +++ b/modules/authentication/proc-enabling-user-authentication-with-github.adoc @@ -8,68 +8,7 @@ Authenticate users with GitHub by provisioning the users and groups from GitHub .Prerequisites * You {configuring-book-link}[added a custom {product-short} application configuration], and have enough permissions to change it. -* You have enough permissions in GitHub to create and manage a link:https://docs.github.com/en/apps/overview[GitHub App]. -Alternatively, you can ask your GitHub administrator to prepare the required GitHub App. - -.Procedure -. To allow {product-short} to authenticate with GitHub, create a GitHub App. -Opt for a GitHub App instead of an OAuth app to use fine-grained permissions and use short-lived tokens. - -.. link:https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app[Register a GitHub App] with the following configuration: - -GitHub App name:: -Enter a unique name identifying your GitHub App, such as `authenticating-with-rhdh-____`. - -Homepage URL:: -Enter your {product-short} URL: `pass:c,a,q[{my-product-url}]`. - -Authorization callback URL:: -Enter your {product-short} authentication backend URL: `pass:c,a,q[{my-product-url}/api/auth/github/handler/frame]`. - -Webhook:: -Clear "Active", as this is not needed for authentication and catalog providers. - -Organization permissions:: -Enable `Read-only` access to *Members*. - -Where can this GitHub App be installed?:: -Select `Only on this account`. - -.. In the *General* -> *Clients secrets* section, click *Generate a new client secret*. - -.. In the *Install App* tab, choose an account to install your GitHub App on. - -.. Save the following values for the next step: - -* **Client ID** -* **Client secret** - -. To add your GitHub credentials to {product-short}, add the following key/value pairs to {configuring-book-link}#provisioning-your-custom-configuration[your {product-short} secrets]. -You can use these secrets in the {product-short} configuration files by using their environment variable name. - -`GITHUB_CLIENT_ID`:: -Enter the saved **Client ID**. - -`GITHUB_CLIENT_SECRET`:: -Enter the saved **Client Secret**. - -`GITHUB_URL`:: -Enter the GitHub host domain: `github.com`. - -`GITHUB_ORG`:: -Enter your GitHub organization name, such as `____`. - -. Enable the GitHub organization provisioning plugin (`backstage-plugin-catalog-backend-module-github-org`). -This plugin imports GitHub users and groups to the {product-short} software catalog. -+ -`dynamic-plugins.yaml` file fragment: -+ -[source,yaml] ----- -plugins: - - package: './dynamic-plugins/dist/backstage-plugin-catalog-backend-module-github-org' - disabled: false ----- +include::snip-enabling-user-authentication-with-github-common-steps.adoc[] . Provision GitHub users and groups to the {product-short} software catalog by adding the `catalog.providers.githubOrg` section to your custom {product-short} `{my-app-config-file}` configuration file: + diff --git a/modules/authentication/snip-enabling-user-authentication-with-github-common-steps.adoc b/modules/authentication/snip-enabling-user-authentication-with-github-common-steps.adoc new file mode 100644 index 0000000000..de3796dc0e --- /dev/null +++ b/modules/authentication/snip-enabling-user-authentication-with-github-common-steps.adoc @@ -0,0 +1,64 @@ +:_mod-docs-content-type: SNIPPET + +* You have enough permissions in GitHub to create and manage a link:https://docs.github.com/en/apps/overview[GitHub App]. +Alternatively, you can ask your GitHub administrator to prepare the required GitHub App. + +.Procedure +. To allow {product-short} to authenticate with GitHub, create a GitHub App. +Opt for a GitHub App instead of an OAuth app to use fine-grained permissions, use short-lived tokens, scale with the number of installations by avoiding rate limits, and have a more transparent integration by avoiding to request user input. + +.. link:https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app[Register a GitHub App] with the following configuration: + +GitHub App name:: +Enter a unique name identifying your GitHub App, such as `authenticating-with-rhdh-____`. + +Homepage URL:: +Enter your {product-short} URL: `pass:c,a,q[{my-product-url}]`. + +Authorization callback URL:: +Enter your {product-short} authentication backend URL: `pass:c,a,q[{my-product-url}/api/auth/github/handler/frame]`. + +Webhook:: +Clear "Active", as this is not needed for authentication and catalog providers. + +Organization permissions:: +Enable `Read-only` access to *Members*. + +Where can this GitHub App be installed?:: +Select `Only on this account`. + +.. In the *General* -> *Clients secrets* section, click *Generate a new client secret*. + +.. In the *Install App* tab, choose an account to install your GitHub App on. + +.. Save the following values for the next step: + +* **Client ID** +* **Client secret** + +. To add your GitHub credentials to {product-short}, add the following key/value pairs to {configuring-book-link}#provisioning-your-custom-configuration[your {product-short} secrets]. +You can use these secrets in the {product-short} configuration files by using their environment variable name. + +`GITHUB_CLIENT_ID`:: +Enter the saved **Client ID**. + +`GITHUB_CLIENT_SECRET`:: +Enter the saved **Client Secret**. + +`GITHUB_URL`:: +Enter the GitHub host domain: `github.com`. + +`GITHUB_ORG`:: +Enter your GitHub organization name, such as `____`. + +. Enable the GitHub organization provisioning plugin (`backstage-plugin-catalog-backend-module-github-org`). +This plugin imports GitHub users and groups to the {product-short} software catalog. ++ +`dynamic-plugins.yaml` file fragment: ++ +[source,yaml] +---- +plugins: + - package: './dynamic-plugins/dist/backstage-plugin-catalog-backend-module-github-org' + disabled: false +---- diff --git a/modules/integrating-with-github/proc-enabling-github-repository-discovery.adoc b/modules/integrating-with-github/proc-enabling-github-repository-discovery.adoc index 1eec0663b6..5cfef2a18f 100644 --- a/modules/integrating-with-github/proc-enabling-github-repository-discovery.adoc +++ b/modules/integrating-with-github/proc-enabling-github-repository-discovery.adoc @@ -11,6 +11,8 @@ If a repository contains a `catalog-info.yaml` file, {product-short} ingests the * You have sufficient permissions in GitHub to create and manage a link:https://docs.github.com/en/apps/overview[GitHub App]. +* To allow users to access GitHub templates or plugins that require GitHub authentication, you have configured GitHub either {authentication-book-link}#enabling-user-authentication-with-github-as-an-auxiliary-authentication-provider[as an auxiliary authentication provider] or {authentication-book-link}#enabling-user-authentication-with-github[as your main authentication provider]. + .Procedure . To allow {product-short} to access the GitHub API, create a GitHub App. Opt for a GitHub App instead of an OAuth app to use fine-grained permissions, gain more control over which repositories the application can access, and use short-lived tokens. From 27fa9236de3e60a85976dfef6dc2099f7d6ff86f Mon Sep 17 00:00:00 2001 From: Judith Magak <124673476+jmagak@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:41:05 +0200 Subject: [PATCH 21/22] RHIDP-9107: Orchestrator architecture (#1422) * Add architecture section * Add architecture section * Add images * Incorporate technical review * Incorporate technical review * Update the section * Apply technical reviews * Apply technical reviews * Apply Janas suggestions * Apply Jana's suggestions * Update texts * Apply new suggestions * Incorporate Fabrice's suggestions * Incorporate Fabrice's suggestions * Apply suggestions * Apply suggestions * Apply suggestions --------- Co-authored-by: GitHub Actions --- assemblies/assembly-orchestrator-rhdh.adoc | 9 +- .../con-architecture-overview.adoc | 36 ++++++++ .../con-orchestrator-plugin-components.adoc | 18 ---- ...pported-architecture-for-orchestrator.adoc | 15 ---- ...bling-orchestrator-plugins-components.adoc | 89 +++++++++++++++++++ 5 files changed, 129 insertions(+), 38 deletions(-) create mode 100644 modules/orchestrator/con-architecture-overview.adoc delete mode 100644 modules/orchestrator/con-orchestrator-plugin-components.adoc delete mode 100644 modules/orchestrator/con-supported-architecture-for-orchestrator.adoc create mode 100644 modules/orchestrator/proc-enabling-orchestrator-plugins-components.adoc diff --git a/assemblies/assembly-orchestrator-rhdh.adoc b/assemblies/assembly-orchestrator-rhdh.adoc index a1612f9678..9d22c3875a 100644 --- a/assemblies/assembly-orchestrator-rhdh.adoc +++ b/assemblies/assembly-orchestrator-rhdh.adoc @@ -27,7 +27,7 @@ To start using Orchestrator in {product-very-short}, you must: * Import the Orchestrator software templates into the {product} catalog // orchestrator architecture -include::modules/orchestrator/con-supported-architecture-for-orchestrator.adoc[leveloffset=+1] +include::modules/orchestrator/con-architecture-overview.adoc[leveloffset=+1] // compatibility guide for {product} Orchestrator include::modules/orchestrator/ref-compatibility-guide-for-orchestrator.adoc[leveloffset=+1] @@ -35,8 +35,8 @@ include::modules/orchestrator/ref-compatibility-guide-for-orchestrator.adoc[leve // provisioning plugin dependencies include::modules/orchestrator/con-orchestrator-plugin-dependencies-operator.adoc[leveloffset=+1] -// installing the components for the orchestrator plugin -include::modules/orchestrator/con-orchestrator-plugin-components.adoc[leveloffset=+1] +// enabling orchestrator plugins components +include::modules/orchestrator/proc-enabling-orchestrator-plugins-components.adoc[leveloffset=+1] // Orchestrator Infrastructure for {product} Helm chart include::modules/orchestrator/proc-helm-install-components-orchestrator-plugin.adoc[leveloffset=+2] @@ -45,5 +45,4 @@ include::modules/orchestrator/proc-helm-install-components-orchestrator-plugin.a include::modules/orchestrator/proc-manual-install-components-orchestrator-plugin.adoc[leveloffset=+2] // {product-very-short} helper script -include::modules/orchestrator/proc-helper-script-overview.adoc[leveloffset=+2] - +include::modules/orchestrator/proc-helper-script-overview.adoc[leveloffset=+2] \ No newline at end of file diff --git a/modules/orchestrator/con-architecture-overview.adoc b/modules/orchestrator/con-architecture-overview.adoc new file mode 100644 index 0000000000..1990dfb9f1 --- /dev/null +++ b/modules/orchestrator/con-architecture-overview.adoc @@ -0,0 +1,36 @@ +:_mod-docs-content-type: CONCEPT + +[id="con-architecture-overview.adoc_{context}"] += Understand Orchestrator architecture + +The Orchestrator architecture is composed of several components, each contributing to the running and management of workflows. + +{product} ({product-very-short}):: Serves as the primary interface. It contains the following subcomponents: + +Orchestrator frontend plugins::: Provide the interface for users to run and monitor workflows within {product-very-short}. + +Orchestrator backend plugins::: Get workflow data into {product-short}. + +Notifications plugins::: Inform users about workflow events. + +Sonataflow:: + +The Sonataflow orchestrator and its subcomponents handle the workflows. +The {product} Orchestrator and the {product} Helm chart manage the following subcomponents lifecycle: + +OpenShift Serverless Logic Operator::: Manages the Sonataflow custom resource (CR), where each CR represents a deployed workflow. + +Sonataflow Runtime/Workflow Application::: Functions as a deployed workflow. Operates as an HTTP server, handling requests for running workflow instances. It is managed as a Kubernetes (K8s) deployment by the Openshift Serverless Logic Operator. + +Data Index Service::: Serves as a repository for workflow definitions, instances, and associated jobs. It exposes a GraphQL API used by the Orchestrator backend plugin to retrieve workflow definitions and instances. +Job Service::: Orchestrates scheduled tasks for workflows. + +OpenShift Serverless::: Provides serverless capabilities essential for workflow communication. It employs Knative eventing to interface with the Data Index service and uses Knative functions to introduce more complex logic to workflows. + +PostgreSQL Server::: Provides a database solution essential for data persistence within the Orchestrator ecosystem. The system uses PostgreSQL Server for storing both Sonataflow information and {product-short} data. + +Keycloak:: Provides authentication and security services within applications. Keycloak must be provisioned externally to manage authentication, as the Orchestrator Operator does not install it. + +OpenShift AMQ Streams (Strimzi/Kafka):: Provides enhanced reliability of the eventing system. Eventing can work without Kafka by using direct HTTP calls, however, this approach is not reliable. ++ +Optional: The current deployment iteration does not natively integrate or include the AMQ Streams Operator. However, you can add the Operator post-install for enhanced reliability if you require it. \ No newline at end of file diff --git a/modules/orchestrator/con-orchestrator-plugin-components.adoc b/modules/orchestrator/con-orchestrator-plugin-components.adoc deleted file mode 100644 index 40f53f85f3..0000000000 --- a/modules/orchestrator/con-orchestrator-plugin-components.adoc +++ /dev/null @@ -1,18 +0,0 @@ -:_mod-docs-content-type: CONCEPT - -[id="con-orchestrator-plugin-components.adoc_{context}"] -= Orchestrator plugin components on {ocp-short} - -To run the Orchestrator plugin successfully on {ocp-short}, you must first install the following required components: - -** {product} ({product-very-short}) {product-custom-resource-type} -** OpenShift Serverless Logic Operator -** OpenShift Serverless Operator -*** Knative Serving -*** Knative Eventing -** (Optional) For managing the Orchestrator project, you need a preinstalled instance of Argo CD or {company-name} OpenShift GitOps in the cluster. It is disabled by default. -** (Optional) To use Tekton tasks and the build pipeline, you need a preinstalled instance of Tekton or {company-name} OpenShift Pipelines in the cluster. These features are disabled by default. - -The most recommended approach to install the dependencies for the Orchestrator plugin is by enabling the dependencies for the Orchestrator plugin directly during your {product-very-short} installation. When you configure {product-very-short} to include the Orchestrator plugin, the {product-very-short} Operator installs the necessary OpenShift Serverless Operators, eliminating the need for separate scripts or Helm charts. - -For specific use cases, you can choose to install the dependencies manually or use helper utilities. diff --git a/modules/orchestrator/con-supported-architecture-for-orchestrator.adoc b/modules/orchestrator/con-supported-architecture-for-orchestrator.adoc deleted file mode 100644 index c5eca5b2ca..0000000000 --- a/modules/orchestrator/con-supported-architecture-for-orchestrator.adoc +++ /dev/null @@ -1,15 +0,0 @@ -:_mod-docs-content-type: CONCEPT - -[id="con-supported-architecture-for-orchestrator_{context}"] -= Supported architecture for Orchestrator - -You can use Orchestrator to design, run, and monitor workflows that automate key tasks. It builds on components like SonataFlow and OpenShift Serverless, which provide the runtime environment and event-driven capabilities needed to power your workflows. - -To help you get started quickly, the following Orchestrator plugin components are included but disabled by default in the `dynamic-plugins.default.yaml` file: - -* `"backstage-plugin-orchestrator"` -* `"backstage-plugin-orchestrator-backend-dynamic"` -* `"backstage-plugin-scaffolder-backend-module-orchestrator-dynamic"` -* `"backstage-plugin-orchestrator-form-widgets"` - -To enable these plugins, set the `plugins.disabled` parameter to `false`. diff --git a/modules/orchestrator/proc-enabling-orchestrator-plugins-components.adoc b/modules/orchestrator/proc-enabling-orchestrator-plugins-components.adoc new file mode 100644 index 0000000000..7fce154dcb --- /dev/null +++ b/modules/orchestrator/proc-enabling-orchestrator-plugins-components.adoc @@ -0,0 +1,89 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-enabling-orchestrator-plugins-components.adoc_{context}"] += Enabling Orchestrator plugins components + +To use the Orchestrator, enable the Orchestrator plugins for {product}, that are disabled by default: + +Orchestrator frontend plugins:: + +`backstage-plugin-orchestrator`::: +Provides the interface for users to run and monitor workflows within {product-very-short}. You can run and track the execution status of processes. + +`backstage-plugin-orchestrator-form-widgets`::: +Provides custom widgets for the workflow execution form, allowing you to customize input fields and streamline the process of launching workflows. + +`backstage-plugin-orchestrator-form`::: +Provides the workflow execution form where you can define and submit the necessary input data required to start a new workflow instance. + +`backstage-plugin-orchestrator-form-api`::: +Defines the API for extending the workflow execution form. + +Orchestrator backend plugins:: + +`backstage-plugin-orchestrator-backend`::: +Gets workflow data into {product-short} making sure {product-very-short} ingests critical workflow metadata and runtime status fulfilling your need for visibility. + +`backstage-plugin-orchestrator-common`::: +Contains the backend OpenAPI specification along with autogenerated API documentation and client libraries. + +`scaffolder-backend-module-orchestrator`::: +Provides callable actions from scaffolder templates, such as `orchestrator:workflow:run` or `orchestrator:workflow:get_params`. + +Notification plugins:: + +`backstage-plugin-notifications`::: +Provides notification frontend components that allow you to display immediate, visible alerts about key workflow state changes, allowing real-time status tracking. + + +`backstage-plugin-signals`::: +Provides notification frontend components user experience enhancements so you can process the real-time lifecycle events. + +`backstage-plugin-notifications-backend-dynamic`::: +Provides notification backend components allowing you to manage and store the stream of workflow events, making sure that critical notifications are ready to be served to the front-end user interface. + +`backstage-plugin-signals-backend-dynamic`::: +Provides the backend components for notification user experience enhancements allowing you to establish the necessary communication channels for the event-driven orchestration that is core to Serverless Workflows. + +.Prerequisites + +* When using the {product} Helm chart, you have installed the necessary OpenShift Serverless Operators. ++ +[NOTE] +==== +When using the {product} Operator, the Operator installs the necessary OpenShift Serverless Operators automatically. For specific use cases, install the dependencies manually or use helper utilities. +==== + +* (Optional) For managing the Orchestrator project, you have an instance of Argo CD or Red Hat OpenShift GitOps in the cluster. It is disabled by default. + +* (Optional) To use Tekton tasks and the build pipeline, you have an instance of Tekton or Red Hat OpenShift Pipelines in the cluster. These features are disabled by default. + +.Procedure +* Locate your {product-short} configuration and enable the Orchestrator plugins and the supporting notification plugins. ++ +[source,yaml,subs="+attributes,+quotes"] +---- +plugins: + - package: "@redhat/backstage-plugin-orchestrator@{product-chart-version}" + disabled: false + - package: "@redhat/backstage-plugin-orchestrator-backend-dynamic@{product-chart-version}" + disabled: false + - package: "@redhat/backstage-plugin-scaffolder-backend-module-orchestrator-dynamic@{product-chart-version}" + disabled: false + - package: "@redhat/backstage-plugin-orchestrator-form-widgets@{product-chart-version}" + disabled: false + - package: "@redhat/backstage-plugin-orchestrator-common@{product-chart-version}" + disabled: false + - package: "@redhat/backstage-plugin-orchestrator-form@{product-chart-version}" + disabled: false + - package: "@redhat/backstage-plugin-orchestrator-form-api@{product-chart-version}" + disabled: false + - package: "./dynamic-plugins/dist/backstage-plugin-notifications" + disabled: false + - package: "./dynamic-plugins/dist/backstage-plugin-signals" + disabled: false + - package: "./dynamic-plugins/dist/backstage-plugin-notifications-backend-dynamic" + disabled: false + - package: "./dynamic-plugins/dist/backstage-plugin-signals-backend-dynamic" + disabled: false +---- \ No newline at end of file From 477884610c211cba7f0245b9ee9373dba2a9f4e5 Mon Sep 17 00:00:00 2001 From: Gerry-Forde <63045020+Gerry-Forde@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:08:30 +0100 Subject: [PATCH 22/22] RHIDP-8635 - Comprehensive documentation for developers on adding localization support to custom plugins --- assemblies/assembly-localization-in-rhdh.adoc | 8 +- .../con-language-persistence.adoc | 23 ++ ...adding-localization-to-custom-plugins.adoc | 301 ++++++++++++++++++ .../proc-customize-rhdh-language.adoc | 2 +- .../proc-enabling-localization-in-rhdh.adoc | 5 +- .../proc-overriding-translations.adoc | 84 +++++ .../proc-select-rhdh-language.adoc | 20 ++ titles/plugins-rhdh-install/master.adoc | 3 + 8 files changed, 440 insertions(+), 6 deletions(-) create mode 100644 modules/customizing-the-appearance/con-language-persistence.adoc create mode 100644 modules/customizing-the-appearance/proc-adding-localization-to-custom-plugins.adoc create mode 100644 modules/customizing-the-appearance/proc-overriding-translations.adoc create mode 100644 modules/customizing-the-appearance/proc-select-rhdh-language.adoc diff --git a/assemblies/assembly-localization-in-rhdh.adoc b/assemblies/assembly-localization-in-rhdh.adoc index 324e2d37cf..02bb79ac51 100644 --- a/assemblies/assembly-localization-in-rhdh.adoc +++ b/assemblies/assembly-localization-in-rhdh.adoc @@ -5,4 +5,10 @@ include::modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc[leveloffset=+1] -include::modules/customizing-the-appearance/proc-customize-rhdh-language.adoc[leveloffset=+1] \ No newline at end of file +include::modules/customizing-the-appearance/proc-overriding-translations.adoc[leveloffset=+2] + +include::modules/customizing-the-appearance/proc-select-rhdh-language.adoc[leveloffset=+1] + +include::modules/customizing-the-appearance/con-language-persistence.adoc[leveloffset=+2] + +include::modules/customizing-the-appearance/proc-adding-localization-to-custom-plugins.adoc[leveloffset=+1] \ No newline at end of file diff --git a/modules/customizing-the-appearance/con-language-persistence.adoc b/modules/customizing-the-appearance/con-language-persistence.adoc new file mode 100644 index 0000000000..8087ff8fc2 --- /dev/null +++ b/modules/customizing-the-appearance/con-language-persistence.adoc @@ -0,0 +1,23 @@ +:_mod-docs-content-type: CONCEPT + +[id="con-language-persistence_{context}"] += Language persistence + +When you change the language in the UI, your preference is saved to storage. On next login or refresh, your chosen language setting is restored. Guest users cannot persist language preferences. + +Default language selection uses the following priority order: + +. *Browser language priority*: The system first checks the user's browser language preferences to provide a personalized experience. + +. *Configuration priority*: If no browser language matches the supported locales, the `defaultLocale` from the `i18n` configuration is used as a fallback. + +. *Fallback priority*: If neither browser preferences nor configuration provide a match, defaults to `en`. + +Red Hat Developer Hub automatically saves and restores user language settings across browser sessions. This feature is enabled by default and uses database storage. To opt-out and use browser storage instead, add the following to your `app-config.yaml`: +[source,yaml,subs="+quotes"] +---- +userSettings: + persistence: browser # <1> +---- +<1> To opt-out and use browser local storage, set this value to `browser`. Optionally, set this value to `database` to persist across browsers and devices. This the default setting and does not require this configuration to be set. + diff --git a/modules/customizing-the-appearance/proc-adding-localization-to-custom-plugins.adoc b/modules/customizing-the-appearance/proc-adding-localization-to-custom-plugins.adoc new file mode 100644 index 0000000000..04b505e1b4 --- /dev/null +++ b/modules/customizing-the-appearance/proc-adding-localization-to-custom-plugins.adoc @@ -0,0 +1,301 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-adding-localization-to-custom-plugins_{context}"] += Adding localization to custom plugins +You can implement localization support in your custom Red Hat Developer Hub plugins so that your plugins are accessible to a diverse, international user base and follow recommended best practices. + +.Procedure +. Create the following translation files in your plugin's `src/translations/ directory`: ++ +.`src/translations/ref.ts` English language reference +[source,json] +---- +src/translations/ref.ts - English Reference +import { createTranslationRef } from "@backstage/core-plugin-api/alpha"; + + +export const myPluginMessages = { + page: { + title: "My Plugin", + subtitle: "Plugin description", + }, + common: { + exportCSV: "Export CSV", + noResults: "No results found", + }, + table: { + headers: { + name: "Name", + count: "Count", + }, + }, +}; + + +export const myPluginTranslationRef = createTranslationRef({ + id: "plugin.my-plugin", + messages: myPluginMessages, +}); +---- + ++ +.`src/translations/de.ts` German language reference +[source,json] +---- +import { createTranslationMessages } from "@backstage/core-plugin-api/alpha"; +import { myPluginTranslationRef } from "./ref"; + + +const myPluginTranslationDe = createTranslationMessages({ + ref: myPluginTranslationRef, + messages: { + "page.title": "Mein Plugin", + "page.subtitle": "Plugin-Beschreibung", + "common.exportCSV": "CSV exportieren", + "common.noResults": "Keine Ergebnisse gefunden", + "table.headers.name": "Name", + "table.headers.count": "Anzahl", + }, +}); + + +export default myPluginTranslationDe; +---- + ++ +.`src/translations/fr.ts` French language reference +[source,json] +---- +import { createTranslationMessages } from "@backstage/core-plugin-api/alpha"; +import { myPluginTranslationRef } from "./ref"; + + +const myPluginTranslationFr = createTranslationMessages({ + ref: myPluginTranslationRef, + messages: { + "page.title": "Mon Plugin", + "page.subtitle": "Description du plugin", + "common.exportCSV": "Exporter CSV", + "common.noResults": "Aucun résultat trouvé", + "table.headers.name": "Nom", + "table.headers.count": "Nombre", + }, +}); + + +export default myPluginTranslationFr; +---- + ++ +.`src/translations/index.ts` translation resource +[source,json] +---- +import { createTranslationResource } from "@backstage/core-plugin-api/alpha"; +import { myPluginTranslationRef } from "./ref"; + + +export const myPluginTranslations = createTranslationResource({ + ref: myPluginTranslationRef, + translations: { + de: () => import("./de"), + fr: () => import("./fr"), + }, +}); + + +export { myPluginTranslationRef }; +---- + +. Create translation hooks ++ +.`src/hooks/useTranslation.ts` translation hooks +[source,json] +---- +src/hooks/useTranslation.ts +import { useTranslationRef } from "@backstage/core-plugin-api/alpha"; +import { myPluginTranslationRef } from "../translations"; + + +export const useTranslation = () => useTranslationRef(myPluginTranslationRef); +---- + +. Update your plugin components to replace hardcoded strings with translation calls: ++ +.Before (hardcoded): +[source,json] +---- +const MyComponent = () => { + return ( +

+ ); +}; +---- ++ +.After (translated): +[source,json] +---- +import { useTranslation } from '../hooks/useTranslation'; + + +const MyComponent = () => { + const { t } = useTranslation(); + + + return ( +
+

{t('page.title')}

+ +
+ ); +}; +---- + +. (Optional) For content with variables, use interpolation: ++ +[source,json] +---- +// In your translation files +'table.pagination.topN': 'Top {{count}} items' +---- + ++ +[source,json] +---- +// In your component +const { t } = useTranslation(); +const message = t('table.pagination.topN', { count: '10' }); +For dynamic translation keys (e.g., from configuration): +// Configuration object with translation keys +const CARD_CONFIGS = [ + { id: 'overview', titleKey: 'cards.overview.title' }, + { id: 'details', titleKey: 'cards.details.title' }, + { id: 'settings', titleKey: 'cards.settings.title' }, +]; + + +// In your component +const { t } = useTranslation(); + + +const CardComponent = ({ config }) => { + return ( +
+

{t(config.titleKey as any)}

+ {/* Use 'as any' for dynamic keys */} +
+ ); +}; +---- + + +. Export translation resources ++ +[source,json] +---- +In your plugin's src/index.ts: +// Export your plugin +export { myPlugin } from "./plugin"; + + +// Export translation resources for RHDH +export { myPluginTranslations, myPluginTranslationRef } from "./translations"; +6. Configure in RHDH +For RHDH (dynamic plugins configuration): +Add to your dynamic-plugins.default.yaml: + +backstage-community.plugin-my-plugin: + translationResources: + - importName: myPluginTranslations + ref: myPluginTranslationRef +For local Backstage app development: +// In your app's App.tsx +import { myPluginTranslations } from "@my-org/backstage-plugin-my-plugin"; + + +const app = createApp({ + apis, + __experimentalTranslations: { + availableLanguages: ["en", "de", "fr"], + resources: [myPluginTranslations], + }, +}); +---- + +. Testing Your Translations ++ +[source,json] +---- +Create test mocks (src/test-utils/mockTranslations.ts): +import { myPluginMessages } from "../translations/ref"; + + +function flattenMessages(obj: any, prefix = ""): Record { + const flattened: Record = {}; + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + const value = obj[key]; + const newKey = prefix ? `${prefix}.${key}` : key; + if (typeof value === "object" && value !== null) { + Object.assign(flattened, flattenMessages(value, newKey)); + } else { + flattened[newKey] = value; + } + } + } + return flattened; +} + + +const flattenedMessages = flattenMessages(myPluginMessages); + + +export const mockT = (key: string, params?: any) => { + let message = flattenedMessages[key] || key; + if (params) { + for (const [paramKey, paramValue] of Object.entries(params)) { + message = message.replace( + new RegExp(`{{${paramKey}}}`, "g"), + String(paramValue), + ); + } + } + return message; +}; + + +export const mockUseTranslation = () => ({ t: mockT }); +Update your tests: +import { mockUseTranslation } from "../test-utils/mockTranslations"; + + +jest.mock("../hooks/useTranslation", () => ({ + useTranslation: mockUseTranslation, +})); +---- + +// Your test code... +== Best practices +* Never modify original English strings - Keep them exactly as they were +* Use flat dot notation in translation files (e.g., 'page.title') +* Use nested objects in the reference file for TypeScript support +* Test with mocks to ensure translations work correctly +* Add all languages to your app configuration + +== Common Patterns +Use Case Pattern Example +Simple text t('key') t('page.title') +With variables t('key', {param}) t('table.topN', {count: '5'}) +Dynamic keys t(config.titleKey as any) t('cards.overview.title' as any) + +== Validation Checklist +* All hardcoded strings replaced with translation calls +* Translation files created for all target languages +* Translation resources exported from src/index.ts +* For RHDH: Dynamic plugins configuration updated with translationResources +* For local app: App configuration updated with available languages +* Tests updated with translation mocks +* Language switching works in the UI +* Fallback to English works for missing translations \ No newline at end of file diff --git a/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc b/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc index b75b598bb7..c0df299ad7 100644 --- a/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc +++ b/modules/customizing-the-appearance/proc-customize-rhdh-language.adoc @@ -1,6 +1,6 @@ :_mod-docs-content-type: PROCEDURE -[id="proc-customizing-rhdh-language_{context}"] +[id="proc-customize-rhdh-language_{context}"] = Customizing the language for your {product-short} instance The language settings of {product-very-short} use English by default. You can choose to use one of the following languages instead. diff --git a/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc b/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc index 0c4acf8a26..5be7cc5254 100644 --- a/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc +++ b/modules/customizing-the-appearance/proc-enabling-localization-in-rhdh.adoc @@ -4,14 +4,11 @@ = Enabling the localization framework in {product-short} Enabling localization enhances accessibility, improves the user experience for a global audience, and assists organizations in meeting language requirements in specific regions. -The language settings of {product-very-short} use English by default. You can choose to use one of the following languages instead. +The language settings of {product-very-short} use English by default. In {product-very-short} {product-version}, you can choose to use one of the following supported languages: -.Supported languages * English (en) * French (fr) -The default language is English, which automatically sets the light or dark theme based on your system preferences. - .Prerequisites .Procedure diff --git a/modules/customizing-the-appearance/proc-overriding-translations.adoc b/modules/customizing-the-appearance/proc-overriding-translations.adoc new file mode 100644 index 0000000000..bda680cf9c --- /dev/null +++ b/modules/customizing-the-appearance/proc-overriding-translations.adoc @@ -0,0 +1,84 @@ +:_mod-docs-content-type: CONCEPT + +[id="prov-overriding-translations_{context}"] += Overriding translations +In {product-very-short} 1.8, English and French are the supported languages. You can implement transalations for other languages by using an `allTranslations.json` file to override existing translation strings and updating the `i18n` section of your `{my-app-config-file}` configuration file. + +.Prerequisitea +* You have enabled localization in your {product-very-short} application. + +.Procedure +. Download the translation strings. +. Create the JSON file containing all your translations, for example: ++ +[id=i18n-enable] +.`allTranslations.json` fragment with translation string overrides +[source,json] +---- +{ + "plugin.npm.translation-ref": { + "en": { + "infoCard.title": "NPM Packet JSON {{packageName}}" + }, + "de": { + "infoCard.title": "NPM Pakettt JSON {{packageName}}" + }, + "zh": { + "infoCard.title": "NPM 包 JSON {{packageName}}" + } + }, + "catalog-import": { + "en": { + "defaultImportPage.headerTitle": "Import an existing Git repository JSON" + }, + "de": { + "defaultImportPage.headerTitle": "Ein vorhandenes Git-Repository importieren JSON" + } + } +} +---- +. Login to your cluster and create a config map for your json translations: ++ +[source,bash] +---- +oc create configmap all-translations --from-file=//allTranslations.json +---- + +. Update your Developer hub helm chart to mount the above config map in the app’s filesystem + +.. In the helm chart -> Root Schema -> Backstage chart schema -> Backstage parameters -> Backstage container additional volume mounts + +.. Select `Add Backstage container additional volume mounts` and add the following ++ +[source,yaml] +---- +MountPath: /opt/app-root/src/translation +Name: all-translations +---- + +.. Add the translations to the `Backstage container additional volumes` in the helm chart ++ +[source,yaml] +---- +name: all-translations +configMap: + defaultMode: 420 + name: all-translations +---- + +. Update the `i18n` section to your custom {product-short} `{my-app-config-file}` configuration file to include the translation override file: ++ +[id=i18n-override] +.`{my-app-config-file}` fragment with localization `i18n` fields +[source,yaml,subs="+quotes"] +---- +i18n: + locales: # List of supported locales. Must include `en`, otherwise the translation framework will fail to load. + - en + - fr + defaultLocale: en # Optional. Defaults to `en` if not specified. + overrides: # List of JSON translation files applied in order (last file wins). Each file may override/add translations for one or more plugins/locales + - /.json + - /.json +---- + diff --git a/modules/customizing-the-appearance/proc-select-rhdh-language.adoc b/modules/customizing-the-appearance/proc-select-rhdh-language.adoc new file mode 100644 index 0000000000..dfa6c335d1 --- /dev/null +++ b/modules/customizing-the-appearance/proc-select-rhdh-language.adoc @@ -0,0 +1,20 @@ +:_mod-docs-content-type: PROCEDURE + +[id="proc-selecting-rhdh-language_{context}"] += Selecting the language for your {product-short} instance + +You can choose to use one of the following supported languages: + +* English (default) +* French + +.Prerequisites + +* You are logged in to the {product-short} web console. + +.Procedure + +. From the {product-short} web console, click *Settings*. +. From the *Appearance* panel, click the language dropdown to select your language of choice. ++ +image::rhdh/customize-language-dropdown.png[] \ No newline at end of file diff --git a/titles/plugins-rhdh-install/master.adoc b/titles/plugins-rhdh-install/master.adoc index fff65e1cf0..427a161ca8 100644 --- a/titles/plugins-rhdh-install/master.adoc +++ b/titles/plugins-rhdh-install/master.adoc @@ -11,6 +11,9 @@ include::artifacts/attributes.adoc[] //installing dynamic plugins include::assemblies/dynamic-plugins/assembly-installing-rhdh-plugins.adoc[leveloffset=+1] +// Converting a custom plugin to a dynamic plugin +include::assemblies/dynamic-plugins/assembly-converting-custom-plugin-to-dynamic-plugin.adoc[leveloffset=+1] + //third-party plugins include::assemblies/dynamic-plugins/assembly-third-party-plugins.adoc[leveloffset=+1]