From 6a661afc10ecb5784cea69fc75ac14c47502ac13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20G=C3=A1mez=2C=20PhD?= Date: Mon, 22 Aug 2022 15:26:39 +0200 Subject: [PATCH 1/8] Fix olm test case (#5227) * update olm version Signed-off-by: Antonio Gamez Diaz * make selector more specific Signed-off-by: Antonio Gamez Diaz * Filter by provider to ensure which operator to install Signed-off-by: Antonio Gamez Diaz Signed-off-by: Antonio Gamez Diaz --- .circleci/config.yml | 2 +- integration/tests/operators/06-operator-deployment.spec.js | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 42977ac04c6..f21c8209843 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -26,7 +26,7 @@ parameters: default: "v3.9.2" OLM_VERSION: type: "string" - default: "v0.21.2" + default: "v0.22.0" CHARTMUSEUM_VERSION: type: "string" default: "3.9.0" diff --git a/integration/tests/operators/06-operator-deployment.spec.js b/integration/tests/operators/06-operator-deployment.spec.js index bbe1c8c984f..6f874c6e188 100644 --- a/integration/tests/operators/06-operator-deployment.spec.js +++ b/integration/tests/operators/06-operator-deployment.spec.js @@ -14,11 +14,14 @@ test("Deploys an Operator", async ({ page }) => { // Go to operators page await page.goto(utils.getUrl("/#/c/default/ns/kubeapps/operators")); - await page.waitForTimeout(10000); + await page.waitForFunction('document.querySelector("cds-progress-circle") === null'); // Select operator to deploy await page.locator("input#search").fill("prometheus"); await page.waitForTimeout(3000); + // using locator with "has" instead of "hasText" to search by this exact name (and exclude others like "Red Hat, Inc.") + await page.locator("cds-checkbox", { has: page.locator('text="Red Hat"') }).click(); + await page.click('a:has-text("prometheus")'); await page.click('cds-button:has-text("Deploy") >> nth=0'); await page.click('cds-button:has-text("Deploy")'); From 414ff3430df3d3178f57340bebef531f26e36765 Mon Sep 17 00:00:00 2001 From: Pepe Baena Date: Mon, 22 Aug 2022 16:28:33 +0200 Subject: [PATCH 2/8] Update Getting started tutorial to comply documentation style guides (#5223) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update Getting started tutorial to comply documentation style guides * Update prerequisites in getting started tutorial Co-authored-by: Antonio Gámez, PhD Co-authored-by: Antonio Gámez, PhD Co-authored-by: Antonio Gámez, PhD --- .../docs/latest/tutorials/getting-started.md | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/site/content/docs/latest/tutorials/getting-started.md b/site/content/docs/latest/tutorials/getting-started.md index 3a7ff2c9dd8..400aec3d29d 100644 --- a/site/content/docs/latest/tutorials/getting-started.md +++ b/site/content/docs/latest/tutorials/getting-started.md @@ -4,13 +4,17 @@ This guide will walk you through the process of deploying Kubeapps for your clus ## Prerequisites -Kubeapps assumes a working Kubernetes cluster (v1.15+), as well as the [`helm`](https://helm.sh/docs/intro/install/) (3.1.0+) and [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) command-line interfaces installed and configured to talk to your Kubernetes cluster. Kubeapps has been tested with Azure Kubernetes Service (AKS), Google Kubernetes Engine (GKE), `minikube` and Docker for Desktop Kubernetes. Kubeapps works on RBAC-enabled clusters and this configuration is encouraged for a more secure install. +- Kubeapps assumes a working Kubernetes cluster (v1.21+), as well as the [`helm`](https://helm.sh/docs/intro/install/) (v3.2.0+) and [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) command-line interfaces installed and configured to talk to your Kubernetes cluster. -> On GKE, you must either be an "Owner" or have the "Container Engine Admin" role in order to install Kubeapps. +- Kubeapps has been tested with Azure Kubernetes Service (AKS), Google Kubernetes Engine (GKE), kind, minikube and Docker for Desktop Kubernetes. + +- Kubeapps works on RBAC-enabled clusters and this configuration is encouraged for a more secure install. + + > On GKE, you must either be an "Owner" or have the "Container Engine Admin" role in order to install Kubeapps. ## Step 1: Install Kubeapps -Use the Helm chart to install the latest version of Kubeapps: +Use the official [Bitnami Kubeapps chart](https://github.com/bitnami/charts/tree/master/bitnami/kubeapps) to install the latest version of Kubeapps: ```bash helm repo add bitnami https://charts.bitnami.com/bitnami @@ -95,23 +99,23 @@ Paste the token generated in the previous step to authenticate and access the Ku **_Note:_** If you are setting up Kubeapps for other people to access, you will want to use a different service type or setup Ingress rather than using the above `kubectl port-forward`. For detailed information on installing, configuring and upgrading Kubeapps, checkout the [chart README](https://github.com/vmware-tanzu/kubeapps/blob/main/chart/kubeapps/README.md) -## Step 4: Deploy WordPress +## Step 4: Deploy applications: WordPress Once you have the Kubeapps Dashboard up and running, you can start deploying applications into your cluster. -- Use the "Deploy" button or click on the "Catalog" page in the Dashboard to select an application from the list of charts in any of the configured Helm chart repositories. This example assumes you want to deploy WordPress. +- Use the **Deploy** button or click on the **Catalog** page in the Dashboard to select an application from the list of packages in any of the configured repositories. This example assumes you want to deploy WordPress. - ![WordPress chart](../img/wordpress-search.png) + ![WordPress search](../img/wordpress-search.png) - Click the "Deploy" button. ![WordPress chart](../img/wordpress-chart.png) -- You will be prompted for the release name and values for the application. The form is populated by the values (YAML), which you can see in the adjacent tab. +- You will be prompted for the release name and values for the application. The form is populated by the values (**YAML**), which you can see in the adjacent tab. ![WordPress installation](../img/wordpress-installation.png) -- Click the "Deploy" button. The application will be deployed. You will be able to track the new Helm deployment directly from the browser. The status will be shown at the top, including the access URL and any secret included with the app. You can also look at the individual resources lower in the page. It will also show the number of ready pods. If you run your cursor over the status, you can see the workloads and number of ready and total pods within them. +- Click the **Deploy** button. The application will be deployed. You will be able to track the new deployment directly from the browser. The status will be shown at the top, including the `access URL` and any `secret` included with the app. You can also look at the individual resources lower in the page. It will also show the number of ready pods. If you run your cursor over the **status**, you can see the workloads and number of ready and total pods within them. ![WordPress deployment](../img/wordpress-deployment.png) @@ -121,7 +125,7 @@ To access your new WordPress site, you can run the commands in the "Notes" secti ![WordPress deployment notes](../img/wordpress-url.png) -To get the credentials for logging into your WordPress account, refer to the "Notes" section. You can also get the WordPress password by clicking on the eye next to `wordpress-password`. +To get the credentials for logging into your WordPress account, refer to the **Notes** section. You can also get the WordPress password by clicking on the eye next to **wordpress-password**. ![WordPress deployment notes](../img/wordpress-credentials.png) @@ -135,8 +139,7 @@ If you want to uninstall/delete your WordPress application, you can do so by cli Learn more about Kubeapps with the links below: -- [Detailed installation instructions](https://github.com/vmware-tanzu/kubeapps/blob/main/chart/kubeapps/README.md) -- [Deploying Operators](./operators.md) -- [Kubeapps Dashboard documentation](../howto/dashboard.md) -- [Project board](https://github.com/orgs/vmware-tanzu/projects/38/views/2) +- [Kubeapps documentation](https://github.com/vmware-tanzu/kubeapps/tree/main/docs) +- [Kubeapps website](https://kubeapps.dev/) - [Roadmap](https://github.com/vmware-tanzu/kubeapps/milestones) +- [Project board](https://github.com/orgs/vmware-tanzu/projects/38/views/2) From 942e4d6a81ded59d03c4bfbf4f3568e0ebe0ff3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20G=C3=A1mez=2C=20PhD?= Date: Mon, 22 Aug 2022 16:52:02 +0200 Subject: [PATCH 3/8] Changes to support OCI with flux in the UI (#5218) * Rename to setSyncInterval to to avoid name collisions Signed-off-by: Antonio Gamez Diaz * Set flux repos as global always Signed-off-by: Antonio Gamez Diaz * Avoid re-selecting storage type if one already selected Signed-off-by: Antonio Gamez Diaz * Enable oci type selector for flux Signed-off-by: Antonio Gamez Diaz * Don't override scheme if non-helm plugins Signed-off-by: Antonio Gamez Diaz * Add test cases Signed-off-by: Antonio Gamez Diaz * handle "always global flux repos" in the isGlobal fn Signed-off-by: Antonio Gamez Diaz * fix test case Signed-off-by: Antonio Gamez Diaz * handle global carvel repos with `carvelGlobalNamespace` Signed-off-by: Antonio Gamez Diaz Signed-off-by: Antonio Gamez Diaz --- dashboard/src/actions/repos.test.tsx | 63 ++++++++++++-- dashboard/src/actions/repos.ts | 8 +- dashboard/src/components/Catalog/Catalog.tsx | 16 +++- .../Config/PkgRepoList/PkgRepoForm.tsx | 84 ++++++++++--------- .../Config/PkgRepoList/PkgRepoList.tsx | 10 ++- .../SelectRepoForm/SelectRepoForm.tsx | 17 +++- dashboard/src/shared/utils.test.ts | 4 +- dashboard/src/shared/utils.ts | 4 +- 8 files changed, 146 insertions(+), 60 deletions(-) diff --git a/dashboard/src/actions/repos.test.tsx b/dashboard/src/actions/repos.test.tsx index 49146ca1fac..e383a8667b6 100644 --- a/dashboard/src/actions/repos.test.tsx +++ b/dashboard/src/actions/repos.test.tsx @@ -23,7 +23,9 @@ import configureMockStore from "redux-mock-store"; import thunk from "redux-thunk"; import { PackageRepositoriesService } from "shared/PackageRepositoriesService"; import PackagesService from "shared/PackagesService"; -import { IPkgRepoFormData, NotFoundError, RepositoryStorageTypes } from "shared/types"; +import { initialState } from "shared/specs/mountWrapper"; +import { IPkgRepoFormData, IStoreState, NotFoundError, RepositoryStorageTypes } from "shared/types"; +import { PluginNames } from "shared/utils"; import { getType } from "typesafe-actions"; import actions from "."; import { convertPkgRepoDetailToSummary } from "./repos"; @@ -32,7 +34,10 @@ const { repos: repoActions } = actions; const mockStore = configureMockStore([thunk]); let store: any; -const plugin = { name: "my.plugin", version: "0.0.1" } as Plugin; +const plugin = { name: PluginNames.PACKAGES_HELM, version: "0.0.1" } as Plugin; +const fluxPlugin = { name: PluginNames.PACKAGES_FLUX, version: "v1beta1" } as Plugin; +const carvelPlugin = { name: PluginNames.PACKAGES_KAPP, version: "v1beta1" } as Plugin; + const packageRepoRef = { identifier: "repo-abc", context: { cluster: "default", namespace: "default" }, @@ -64,19 +69,29 @@ const packageRepositoryDetail = { const kubeappsNamespace = "kubeapps-namespace"; const globalReposNamespace = "kubeapps-repos-global"; +const carvelGlobalNamespace = "carvel-repos-global"; beforeEach(() => { store = mockStore({ - config: { kubeappsNamespace, globalReposNamespace }, + config: { + ...initialState.config, + kubeappsNamespace, + globalReposNamespace, + carvelGlobalNamespace, + }, clusters: { + ...initialState.clusters, currentCluster: "default", clusters: { + ...initialState.clusters.clusters, default: { + ...initialState.clusters.clusters[initialState.clusters.currentCluster], currentNamespace: kubeappsNamespace, }, }, }, - }); + } as Partial); + PackageRepositoriesService.getPackageRepositorySummaries = jest .fn() .mockImplementationOnce(() => { @@ -314,7 +329,7 @@ describe("fetchRepoSummaries", () => { }, { type: getType(repoActions.requestRepoSummaries), - payload: globalReposNamespace, + payload: "", }, { type: getType(repoActions.receiveRepoSummaries), @@ -352,7 +367,7 @@ describe("fetchRepoSummaries", () => { }, { type: getType(repoActions.requestRepoSummaries), - payload: globalReposNamespace, + payload: "", }, { type: getType(repoActions.receiveRepoSummaries), @@ -489,6 +504,42 @@ describe("addRepo", () => { const res = await store.dispatch(addRepoCMDAuth); expect(res).toBe(true); }); + + it("sets flux repos as global", async () => { + await store.dispatch( + repoActions.addRepo("my-namespace", { + ...pkgRepoFormData, + plugin: fluxPlugin as Plugin, + }), + ); + expect(PackageRepositoriesService.addPackageRepository).toHaveBeenCalledWith( + "default", + "my-namespace", + { + ...pkgRepoFormData, + plugin: fluxPlugin, + }, + false, + ); + }); + + it("sets carvel repos as global if using the carvelGlobalNamespace", async () => { + await store.dispatch( + repoActions.addRepo(carvelGlobalNamespace, { + ...pkgRepoFormData, + plugin: carvelPlugin as Plugin, + }), + ); + expect(PackageRepositoriesService.addPackageRepository).toHaveBeenCalledWith( + "default", + carvelGlobalNamespace, + { + ...pkgRepoFormData, + plugin: carvelPlugin, + }, + false, + ); + }); }); context("when authHeader and customCA are empty", () => { diff --git a/dashboard/src/actions/repos.ts b/dashboard/src/actions/repos.ts index d9693228274..5b82a13b92b 100644 --- a/dashboard/src/actions/repos.ts +++ b/dashboard/src/actions/repos.ts @@ -69,7 +69,7 @@ export const fetchRepoSummaries = ( return async (dispatch, getState) => { const { clusters: { currentCluster }, - config: { globalReposNamespace }, + config: { globalReposNamespace, carvelGlobalNamespace }, } = getState(); try { dispatch(requestRepoSummaries(namespace)); @@ -77,12 +77,14 @@ export const fetchRepoSummaries = ( cluster: currentCluster, namespace: namespace, }); - if (!listGlobal || namespace === globalReposNamespace) { + if (!listGlobal || [globalReposNamespace, carvelGlobalNamespace].includes(namespace)) { dispatch(receiveRepoSummaries(repos.packageRepositorySummaries)); } else { // Global repos need to be added + // instead of passing each global repo's namespace, we defer the decision to the backend using ns="" + // however, this can cause issues when using unprivileged users, see #5215 let totalRepos = repos.packageRepositorySummaries; - dispatch(requestRepoSummaries(globalReposNamespace)); + dispatch(requestRepoSummaries("")); const globalRepos = await PackageRepositoriesService.getPackageRepositorySummaries({ cluster: currentCluster, namespace: "", diff --git a/dashboard/src/components/Catalog/Catalog.tsx b/dashboard/src/components/Catalog/Catalog.tsx index 88450b516b3..1bea2ff24ee 100644 --- a/dashboard/src/components/Catalog/Catalog.tsx +++ b/dashboard/src/components/Catalog/Catalog.tsx @@ -88,7 +88,13 @@ export default function Catalog() { }, operators, repos: { reposSummaries: repos }, - config: { appVersion, kubeappsCluster, globalReposNamespace, featureFlags }, + config: { + appVersion, + kubeappsCluster, + globalReposNamespace, + carvelGlobalNamespace, + featureFlags, + }, } = useSelector((state: IStoreState) => state); const { cluster, namespace } = ReactRouter.useParams() as IRouteParams; const location = ReactRouter.useLocation(); @@ -240,7 +246,11 @@ export default function Catalog() { // We do not currently support package repositories on additional clusters. const supportedCluster = cluster === kubeappsCluster; useEffect(() => { - if (!namespace || !supportedCluster || namespace === globalReposNamespace) { + if ( + !namespace || + !supportedCluster || + [globalReposNamespace, carvelGlobalNamespace].includes(namespace) + ) { // All Namespaces. Global namespace or other cluster, show global repos only dispatch(actions.repos.fetchRepoSummaries("")); return () => {}; @@ -248,7 +258,7 @@ export default function Catalog() { // In other case, fetch global and namespace repos dispatch(actions.repos.fetchRepoSummaries(namespace, true)); return () => {}; - }, [dispatch, supportedCluster, namespace, globalReposNamespace]); + }, [dispatch, supportedCluster, namespace, globalReposNamespace, carvelGlobalNamespace]); useEffect(() => { // Ignore operators if specified diff --git a/dashboard/src/components/Config/PkgRepoList/PkgRepoForm.tsx b/dashboard/src/components/Config/PkgRepoList/PkgRepoForm.tsx index 9a5c9fcafad..b400b13280f 100644 --- a/dashboard/src/components/Config/PkgRepoList/PkgRepoForm.tsx +++ b/dashboard/src/components/Config/PkgRepoList/PkgRepoForm.tsx @@ -150,7 +150,7 @@ export function PkgRepoForm(props: IPkgRepoFormProps) { const [filterRegex, setFilterRegex] = useState(false); // -- Advanced variables -- - const [interval, setInterval] = useState(initialInterval); + const [syncInterval, setSyncInterval] = useState(initialInterval); const [performValidation, setPerformValidation] = useState(true); const [customCA, setCustomCA] = useState(""); const [skipTLS, setSkipTLS] = useState(!!repo?.tlsConfig?.insecureSkipVerify); @@ -186,7 +186,7 @@ export function PkgRepoForm(props: IPkgRepoFormProps) { setDescription(repo.description); setSkipTLS(!!repo.tlsConfig?.insecureSkipVerify); setPassCredentials(!!repo.auth?.passCredentials); - setInterval(repo.interval); + setSyncInterval(repo.interval); setCustomCA(repo.tlsConfig?.certAuthority || ""); setAuthCustomHeader(repo.auth?.header || ""); setBearerToken(repo.auth?.header || ""); @@ -278,10 +278,10 @@ export function PkgRepoForm(props: IPkgRepoFormProps) { ? ociRepositories?.split(",").map(r => r.trim()) : []; - // If the scheme is not specified, assume HTTPS. This is common for OCI registries - // unless using the kapp plugin, which explicitly should not include https:// protocol prefix + // In the Helm plugin, if the scheme is not specified, assume HTTPS (also for OCI registries) + // Other plugins don't allow passing a scheme (eg. carvel) and others require a different one (eg. flux: oci://) let finalURL = url; - if (plugin?.name !== PluginNames.PACKAGES_KAPP && !url?.startsWith("http")) { + if (plugin?.name === PluginNames.PACKAGES_HELM && !url?.startsWith("http")) { finalURL = `https://${url}`; } @@ -329,7 +329,7 @@ export function PkgRepoForm(props: IPkgRepoFormProps) { password: !isUserManagedSecret ? secretPassword : "", server: !isUserManagedSecret ? secretServer : "", } as DockerCredentials, - interval, + interval: syncInterval, name, passCredentials, plugin, @@ -378,7 +378,7 @@ export function PkgRepoForm(props: IPkgRepoFormProps) { setDescription(e.target.value); }; const handleIntervalChange = (e: React.ChangeEvent) => { - setInterval(e.target.value); + setSyncInterval(e.target.value); }; const handlePerformValidationChange = (_e: React.ChangeEvent) => { setPerformValidation(!performValidation); @@ -431,19 +431,27 @@ export function PkgRepoForm(props: IPkgRepoFormProps) { setPlugin(getPluginByName(e.target.value)); // set some default values based on the selected plugin switch (getPluginByName(e.target.value)?.name) { - case PluginNames.PACKAGES_HELM: - setType(RepositoryStorageTypes.PACKAGE_REPOSITORY_STORAGE_HELM); + case PluginNames.PACKAGES_HELM: { + if (!type) { + setType(RepositoryStorageTypes.PACKAGE_REPOSITORY_STORAGE_HELM); + } // helm plugin doesn't allow interval // eslint-disable-next-line no-implied-eval - setInterval(""); + setSyncInterval(""); break; - case PluginNames.PACKAGES_FLUX: - setType(RepositoryStorageTypes.PACKAGE_REPOSITORY_STORAGE_HELM); - setInterval(interval || initialInterval); + } + case PluginNames.PACKAGES_FLUX: { + if (!type) { + setType(RepositoryStorageTypes.PACKAGE_REPOSITORY_STORAGE_HELM); + } + setSyncInterval(syncInterval || initialInterval); break; + } case PluginNames.PACKAGES_KAPP: - setType(RepositoryStorageTypes.PACKAGE_REPOSITORY_STORAGE_CARVEL_IMGPKGBUNDLE); - setInterval(interval || initialInterval); + if (!type) { + setType(RepositoryStorageTypes.PACKAGE_REPOSITORY_STORAGE_CARVEL_IMGPKGBUNDLE); + } + setSyncInterval(syncInterval || initialInterval); break; } }; @@ -709,8 +717,7 @@ export function PkgRepoForm(props: IPkgRepoFormProps) { id="kubeapps-repo-type-oci" type="radio" name="type" - // TODO(agamez): workaround until Flux plugin also supports OCI artifacts - disabled={plugin?.name === PluginNames.PACKAGES_FLUX || !!repo?.type} + disabled={!!repo?.type} value={RepositoryStorageTypes.PACKAGE_REPOSITORY_STORAGE_OCI || ""} checked={type === RepositoryStorageTypes.PACKAGE_REPOSITORY_STORAGE_OCI} onChange={handleTypeRadioButtonChange} @@ -1656,35 +1663,34 @@ export function PkgRepoForm(props: IPkgRepoFormProps) { id="panel-filtering" expanded={accordion[2]} hidden={ - type !== RepositoryStorageTypes.PACKAGE_REPOSITORY_STORAGE_OCI && + type !== RepositoryStorageTypes.PACKAGE_REPOSITORY_STORAGE_OCI || plugin?.name !== PluginNames.PACKAGES_HELM } > toggleAccordion(2)}>Filtering - {type === RepositoryStorageTypes.PACKAGE_REPOSITORY_STORAGE_OCI && ( - - - - Include a list of comma-separated OCI repositories that will be available in - Kubeapps. - -