@rancher/shell 3.0.12-rc.2 → 3.0.12-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apis/impl/apis.ts +6 -0
- package/apis/index.ts +26 -0
- package/apis/intf/resources-api/cluster-api.ts +18 -0
- package/apis/intf/resources-api/mgmt-api.ts +15 -0
- package/apis/intf/resources-api/resource-base.ts +107 -0
- package/apis/intf/resources-api/resource-constants.ts +147 -0
- package/apis/intf/resources-api/resources-api.ts +143 -0
- package/apis/intf/resources.ts +49 -0
- package/apis/intf/{modal.ts → shell-api/modal.ts} +21 -26
- package/apis/intf/shell-api/proxy.ts +216 -0
- package/apis/intf/{slide-in.ts → shell-api/slide-in.ts} +4 -3
- package/apis/intf/{system.ts → shell-api/system.ts} +4 -1
- package/apis/intf/shell.ts +12 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +550 -0
- package/apis/resources/index.ts +22 -0
- package/apis/resources/resources-api-class.ts +187 -0
- package/apis/shell/__tests__/proxy.test.ts +369 -0
- package/apis/shell/index.ts +8 -1
- package/apis/shell/modal.ts +4 -1
- package/apis/shell/notifications.ts +9 -6
- package/apis/shell/proxy.ts +256 -0
- package/apis/shell/slide-in.ts +4 -1
- package/apis/vue-shim.d.ts +2 -1
- package/assets/data/aws-regions.json +4 -0
- package/assets/fonts/lato/LatoLatin-Black.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Black.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-BlackItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-BlackItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Bold.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Bold.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-BoldItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-BoldItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Heavy.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Heavy.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-HeavyItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-HeavyItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Italic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Italic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Light.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Light.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-LightItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-LightItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Medium.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Medium.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-MediumItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-MediumItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Regular.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Regular.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Semibold.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Semibold.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff2 +0 -0
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/fonts/_fontstack.scss +132 -8
- package/assets/styles/global/_layout.scss +4 -0
- package/assets/translations/en-us.yaml +165 -45
- package/assets/translations/zh-hans.yaml +1 -7
- package/chart/monitoring/ClusterSelector.vue +0 -21
- package/chart/monitoring/index.vue +10 -1
- package/chart/monitoring/prometheus/index.vue +6 -3
- package/components/ActionDropdownShell.vue +2 -1
- package/components/CruResource.vue +161 -14
- package/components/CruResourceFooter.vue +9 -5
- package/components/ExplorerMembers.vue +8 -4
- package/components/ExplorerProjectsNamespaces.vue +11 -7
- package/components/GrowlManager.vue +4 -0
- package/components/InstallHelmCharts.vue +2 -2
- package/components/LandingPagePreference.vue +14 -5
- package/components/MgmtNodeList.vue +184 -0
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
- package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
- package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
- package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +17 -1
- package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
- package/components/Resource/Detail/Metadata/index.vue +6 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
- package/components/Resource/Detail/SpacedRow.vue +3 -1
- package/components/Resource/Detail/TitleBar/index.vue +10 -11
- package/components/ResourceDetail/index.vue +1 -1
- package/components/ResourceList/Masthead.vue +19 -9
- package/components/ResourceList/index.vue +82 -1
- package/components/RichTranslation.vue +5 -2
- package/components/SelectIconGrid.vue +0 -10
- package/components/Setting.vue +1 -0
- package/components/SingleClusterInfo.vue +1 -0
- package/components/SortableTable/__tests__/sorting.test.ts +126 -0
- package/components/SortableTable/index.vue +6 -9
- package/components/SortableTable/selection.js +23 -5
- package/components/SortableTable/sorting.js +6 -3
- package/components/SubtleLink.vue +31 -6
- package/components/Tabbed/Tab.vue +29 -3
- package/components/Tabbed/index.vue +25 -3
- package/components/TableOfContents/TableOfContents.vue +109 -0
- package/components/TableOfContents/composables.ts +258 -0
- package/components/Window/ContainerShell.vue +21 -11
- package/components/Window/__tests__/ContainerShell.test.ts +107 -37
- package/components/Wizard.vue +23 -17
- package/components/fleet/AppCoChartGrid.vue +401 -0
- package/components/fleet/AppCoEmptyState.vue +127 -0
- package/components/fleet/AppCoPageHeader.vue +119 -0
- package/components/fleet/AppCoVersionSelect.vue +70 -0
- package/components/fleet/FleetBundles.vue +100 -12
- package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
- package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
- package/components/fleet/FleetClusterTargets/index.vue +226 -161
- package/components/fleet/FleetIntro.vue +7 -3
- package/components/fleet/FleetNoWorkspaces.vue +7 -3
- package/components/fleet/FleetSecretSelector.vue +5 -3
- package/components/fleet/FleetValuesFrom.vue +8 -2
- package/components/fleet/GitRepoTargetTab.vue +0 -2
- package/components/fleet/HelmOpAdvancedTab.vue +19 -53
- package/components/fleet/HelmOpAppCoConfigTab.vue +593 -0
- package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
- package/components/fleet/HelmOpResourcesSection.vue +82 -0
- package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
- package/components/fleet/HelmOpTargetTab.vue +64 -60
- package/components/fleet/HelmOpValuesTab.vue +129 -105
- package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
- package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
- package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +402 -115
- package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
- package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
- package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
- package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
- package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
- package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
- package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
- package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
- package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
- package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
- package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
- package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
- package/components/fleet/dashboard/Empty.vue +8 -4
- package/components/fleet/dashboard/ResourceCard.vue +28 -0
- package/components/fleet/dashboard/ResourceDetails.vue +28 -0
- package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
- package/components/form/ArrayList.vue +61 -4
- package/components/form/KeyValue.vue +23 -2
- package/components/form/LabeledSelect.vue +59 -4
- package/components/form/Labels.vue +22 -3
- package/components/form/NameNsDescription.vue +24 -5
- package/components/form/ResourceTabs/index.vue +1 -0
- package/components/form/Security.vue +6 -2
- package/components/form/WorkloadPorts.vue +2 -7
- package/components/form/__tests__/NameNsDescription.test.ts +75 -0
- package/components/form/__tests__/Security.test.ts +76 -0
- package/components/formatter/Autoscaler.vue +4 -4
- package/components/formatter/ClusterKubeVersion.vue +27 -0
- package/components/formatter/ClusterLink.vue +1 -7
- package/components/formatter/ClusterProvider.vue +6 -10
- package/components/formatter/FleetSummaryGraph.vue +0 -3
- package/components/formatter/InternalExternalIP.vue +10 -4
- package/components/formatter/MachineSummaryGraph.vue +1 -1
- package/components/formatter/PodsUsage.vue +2 -2
- package/components/formatter/ServiceTargets.vue +26 -7
- package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
- package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
- package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
- package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
- package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
- package/components/nav/Header.vue +4 -0
- package/components/nav/NamespaceFilter.vue +2 -2
- package/components/nav/TopLevelMenu.helper.ts +15 -3
- package/components/nav/TopLevelMenu.vue +22 -6
- package/components/nav/__tests__/Header.test.ts +15 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +263 -21
- package/components/templates/default.vue +9 -4
- package/components/templates/home.vue +23 -0
- package/components/templates/plain.vue +23 -0
- package/components/templates/standalone.vue +17 -0
- package/composables/useFormValidation.ts +93 -0
- package/composables/useHelmOpResources.test.ts +56 -0
- package/composables/useHelmOpResources.ts +32 -0
- package/composables/useStateColor.test.ts +325 -0
- package/composables/useStateColor.ts +128 -0
- package/composables/useVeeValidateField.test.ts +159 -0
- package/composables/useVeeValidateField.ts +67 -0
- package/config/home-links.js +1 -1
- package/config/labels-annotations.js +1 -0
- package/config/pagination-table-headers.js +18 -1
- package/config/product/explorer.js +17 -4
- package/config/product/manager.js +84 -21
- package/config/router/index.js +16 -0
- package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
- package/config/router/navigation-guards/authentication.js +10 -4
- package/config/router/routes.js +26 -6
- package/config/settings.ts +0 -2
- package/config/table-headers.js +23 -5
- package/config/types.js +11 -1
- package/core/__tests__/plugin-products.test.ts +904 -20
- package/core/plugin-products-base.ts +110 -10
- package/core/plugin-products.ts +4 -0
- package/core/plugin-types.ts +194 -31
- package/core/plugin.ts +18 -7
- package/core/productDebugger.js +9 -4
- package/core/types-provisioning.ts +77 -31
- package/core/types.ts +72 -22
- package/detail/__tests__/pod.test.ts +41 -0
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
- package/detail/__tests__/workload.test.ts +3 -152
- package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
- package/detail/harvesterhci.io.management.cluster.vue +6 -2
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +34 -14
- package/detail/workload/index.vue +12 -55
- package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +105 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
- package/edit/auth/__tests__/azuread.test.ts +247 -39
- package/edit/auth/__tests__/github.test.ts +234 -0
- package/edit/auth/__tests__/oidc.test.ts +26 -6
- package/edit/auth/__tests__/saml.test.ts +196 -0
- package/edit/auth/azuread.vue +197 -56
- package/edit/auth/github.vue +72 -13
- package/edit/auth/ldap/__tests__/index.test.ts +206 -0
- package/edit/auth/ldap/config.vue +8 -0
- package/edit/auth/ldap/index.vue +75 -1
- package/edit/auth/oidc.vue +119 -73
- package/edit/auth/saml.vue +76 -12
- package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
- package/edit/fleet.cattle.io.helmop.vue +491 -136
- package/edit/management.cattle.io.user.vue +5 -2
- package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
- package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
- package/edit/networking.k8s.io.ingress/index.vue +75 -20
- package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +11 -7
- package/edit/provisioning.cattle.io.cluster/rke2.vue +92 -14
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +22 -0
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
- package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
- package/edit/secret/__tests__/ssh.test.ts +5 -6
- package/edit/secret/basic.vue +31 -0
- package/edit/secret/index.vue +68 -17
- package/edit/secret/registry.vue +38 -0
- package/edit/secret/ssh.vue +29 -0
- package/edit/secret/tls.vue +30 -0
- package/edit/service.vue +4 -4
- package/edit/workload/Upgrading.vue +3 -3
- package/edit/workload/__tests__/Upgrading.test.ts +6 -9
- package/edit/workload/mixins/workload.js +2 -1
- package/list/fleet.cattle.io.bundle.vue +7 -104
- package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
- package/list/group.principal.vue +5 -4
- package/list/harvesterhci.io.management.cluster.vue +8 -9
- package/list/management.cattle.io.user.vue +12 -9
- package/list/provisioning.cattle.io.cluster.vue +268 -180
- package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
- package/mixins/__tests__/auth-config.test.ts +90 -0
- package/mixins/__tests__/chart.test.ts +206 -0
- package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
- package/mixins/auth-config.js +7 -0
- package/mixins/brand.js +2 -1
- package/mixins/chart.js +22 -9
- package/mixins/child-hook.js +12 -6
- package/mixins/create-edit-view/impl.js +5 -3
- package/mixins/resource-fetch-api-pagination.js +62 -6
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
- package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
- package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
- package/models/__tests__/fleet-application.test.ts +175 -0
- package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
- package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
- package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
- package/models/__tests__/management.cattle.io.node.ts +28 -5
- package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
- package/models/__tests__/namespace.test.ts +36 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +81 -11
- package/models/__tests__/workload.test.ts +401 -26
- package/models/base-cluster.x-k8s.io.js +26 -0
- package/models/catalog.cattle.io.clusterrepo.js +28 -4
- package/models/cluster.js +1 -1
- package/models/cluster.x-k8s.io.machine.js +4 -22
- package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
- package/models/cluster.x-k8s.io.machineset.js +2 -20
- package/models/compliance.cattle.io.clusterscan.js +165 -2
- package/models/ext.cattle.io.kubeconfig.ts +4 -7
- package/models/fleet-application.js +7 -1
- package/models/fleet.cattle.io.helmop.js +20 -1
- package/models/management.cattle.io.cluster.js +434 -41
- package/models/management.cattle.io.node.js +50 -7
- package/models/management.cattle.io.nodepool.js +1 -1
- package/models/namespace.js +1 -1
- package/models/networking.k8s.io.ingress.js +12 -4
- package/models/pod.js +33 -1
- package/models/provisioning.cattle.io.cluster.js +51 -334
- package/models/rke.cattle.io.etcdsnapshot.js +1 -2
- package/models/workload.js +108 -13
- package/models/workload.service.js +5 -0
- package/package.json +22 -39
- package/pages/__tests__/readme.test.ts +49 -0
- package/pages/about.vue +5 -6
- package/pages/auth/login.vue +0 -35
- package/pages/auth/setup.vue +13 -3
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +76 -0
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
- package/pages/c/_cluster/apps/charts/chart.vue +62 -9
- package/pages/c/_cluster/apps/charts/index.vue +48 -10
- package/pages/c/_cluster/apps/charts/install.vue +122 -113
- package/pages/c/_cluster/auth/roles/index.vue +5 -4
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
- package/pages/c/_cluster/explorer/index.vue +5 -49
- package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
- package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
- package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
- package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
- package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
- package/pages/c/_cluster/fleet/application/create.vue +187 -136
- package/pages/c/_cluster/fleet/application/index.vue +5 -3
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
- package/pages/c/_cluster/fleet/index.vue +2 -2
- package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
- package/pages/c/_cluster/istio/index.vue +21 -6
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -0
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +815 -2
- package/pages/c/_cluster/uiplugins/index.vue +218 -197
- package/pages/diagnostic.vue +13 -17
- package/pages/fail-whale.vue +30 -7
- package/pages/home.vue +93 -306
- package/pages/readme.vue +88 -0
- package/plugins/clean-html.d.ts +9 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +181 -0
- package/plugins/dashboard-store/actions.js +40 -18
- package/plugins/dashboard-store/resource-class.js +67 -9
- package/plugins/steve/__tests__/actions.test.ts +212 -0
- package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
- package/plugins/steve/actions.js +96 -0
- package/plugins/steve/steve-pagination-utils.ts +12 -4
- package/plugins/steve/subscribe.js +35 -5
- package/rancher-components/Accordion/Accordion.vue +53 -9
- package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +10 -4
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -52
- package/rancher-components/Form/Radio/RadioButton.vue +17 -1
- package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
- package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
- package/rancher-components/RcButton/RcButton.test.ts +140 -1
- package/rancher-components/RcButton/RcButton.vue +126 -17
- package/rancher-components/RcButton/types.ts +3 -0
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
- package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
- package/rancher-components/RcSection/RcSection.vue +28 -3
- package/scripts/extension/helm/package/Dockerfile +1 -1
- package/scripts/test-plugins-build.sh +2 -1
- package/store/__tests__/catalog.test.ts +115 -1
- package/store/__tests__/notifications.test.ts +434 -0
- package/store/__tests__/type-map.test.ts +556 -1
- package/store/action-menu.js +8 -3
- package/store/auth.js +1 -1
- package/store/aws.js +27 -16
- package/store/catalog.js +84 -3
- package/store/digitalocean.js +20 -38
- package/store/index.js +2 -0
- package/store/linode.js +25 -40
- package/store/plugins.js +7 -4
- package/store/pnap.js +1 -0
- package/store/type-map.js +111 -29
- package/tsconfig.paths.json +8 -8
- package/types/components/buttonGroup.ts +5 -0
- package/types/kube/kube-api.ts +14 -1
- package/types/rancher/steve.api.ts +12 -12
- package/types/resources/settings.d.ts +2 -1
- package/types/shell/index.d.ts +206 -72
- package/types/store/dashboard-store.types.ts +108 -11
- package/types/store/pagination.types.ts +6 -3
- package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
- package/utils/__tests__/async.test.ts +87 -0
- package/utils/__tests__/auth.test.ts +273 -0
- package/utils/__tests__/aws.test.ts +140 -0
- package/utils/__tests__/banners.test.ts +176 -0
- package/utils/__tests__/chart.test.ts +64 -1
- package/utils/__tests__/color.test.ts +226 -0
- package/utils/__tests__/computed.test.ts +193 -0
- package/utils/__tests__/cspAdaptor.test.ts +163 -0
- package/utils/__tests__/dom.test.ts +81 -0
- package/utils/__tests__/duration.test.ts +176 -0
- package/utils/__tests__/dynamic-importer.test.ts +102 -0
- package/utils/__tests__/fleet-appco.test.ts +312 -0
- package/utils/__tests__/fleet.test.ts +340 -0
- package/utils/__tests__/ingress.test.ts +553 -0
- package/utils/__tests__/kube.test.ts +68 -0
- package/utils/__tests__/monitoring.test.ts +130 -0
- package/utils/__tests__/namespace-filter.test.ts +109 -0
- package/utils/__tests__/object.test.ts +22 -0
- package/utils/__tests__/pagination-utils.test.ts +361 -0
- package/utils/__tests__/parse-externalid.test.ts +137 -0
- package/utils/__tests__/perf-setting.utils.test.ts +98 -0
- package/utils/__tests__/platform.test.ts +91 -0
- package/utils/__tests__/poller-sequential.test.ts +177 -0
- package/utils/__tests__/poller.test.ts +170 -0
- package/utils/__tests__/position.test.ts +237 -0
- package/utils/__tests__/promise.test.ts +346 -0
- package/utils/__tests__/provider.test.ts +51 -1
- package/utils/__tests__/queue.test.ts +232 -0
- package/utils/__tests__/release-notes.test.ts +221 -0
- package/utils/__tests__/router.test.js +254 -1
- package/utils/__tests__/select.test.ts +208 -0
- package/utils/__tests__/settings.test.ts +140 -0
- package/utils/__tests__/sort-utils.test.ts +301 -0
- package/utils/__tests__/string-utils.test.ts +798 -0
- package/utils/__tests__/string.test.ts +23 -1
- package/utils/__tests__/style.test.ts +154 -0
- package/utils/__tests__/svg-filter.test.ts +184 -0
- package/utils/__tests__/time.test.ts +265 -1
- package/utils/__tests__/title.test.ts +47 -0
- package/utils/__tests__/units.test.ts +417 -0
- package/utils/__tests__/versions.test.ts +128 -0
- package/utils/__tests__/width.test.ts +53 -0
- package/utils/__tests__/window.test.ts +158 -0
- package/utils/__tests__/xccdf.test.ts +511 -0
- package/utils/chart.js +36 -0
- package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
- package/utils/crypto/__tests__/index.test.ts +144 -0
- package/utils/duration.ts +104 -0
- package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
- package/utils/dynamic-content/info.ts +2 -1
- package/utils/error.js +13 -0
- package/utils/fleet-appco.ts +323 -0
- package/utils/fleet.ts +13 -3
- package/utils/gatekeeper/__tests__/util.test.ts +174 -0
- package/utils/gc/__tests__/gc-interval.test.ts +119 -0
- package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
- package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
- package/utils/gc/__tests__/gc.test.ts +487 -0
- package/utils/ingress.ts +9 -1
- package/utils/object.js +22 -2
- package/utils/pagination-utils.ts +2 -1
- package/utils/provider.ts +12 -0
- package/utils/string.js +25 -2
- package/utils/uiplugins.ts +5 -5
- package/utils/validators/__tests__/cluster-name.test.ts +110 -0
- package/utils/validators/__tests__/container-images.test.ts +104 -0
- package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
- package/utils/validators/__tests__/flow-output.test.ts +91 -0
- package/utils/validators/__tests__/index.test.ts +481 -0
- package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
- package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
- package/utils/validators/__tests__/misc-validators.test.ts +246 -0
- package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
- package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
- package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
- package/utils/validators/__tests__/role-template.test.ts +149 -0
- package/utils/validators/__tests__/service.test.ts +283 -0
- package/utils/validators/__tests__/setting.test.js +32 -0
- package/utils/validators/formRules/__tests__/index.test.ts +50 -0
- package/utils/validators/formRules/index.ts +5 -5
- package/utils/validators/machine-pool.ts +1 -1
- package/utils/validators/setting.js +18 -3
- package/utils/xccdf.ts +415 -0
- package/vue.config.js +1 -1
- package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
- package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
- package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
- package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
- package/pages/support/index.vue +0 -264
- package/utils/duration.js +0 -43
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { base64Encode } from '@shell/utils/crypto';
|
|
2
|
+
import { CATALOG as CATALOG_TYPES, SECRET } from '@shell/config/types';
|
|
3
|
+
import { CATALOG, DESCRIPTION, FLEET as FLEET_LABELS } from '@shell/config/labels-annotations';
|
|
4
|
+
import { SECRET_TYPES } from '@shell/config/secret';
|
|
5
|
+
|
|
6
|
+
export const SUSE_APP_COLLECTION_REPO_URL = 'oci://dp.apps.rancher.io/charts';
|
|
7
|
+
export const FLEET_APPCO_AUTH_GENERATE_NAME = 'fleet-appco-auth-';
|
|
8
|
+
export const IMAGE_PULL_SECRET_SUFFIX = '-image-pull-secret';
|
|
9
|
+
export const SUSE_APPCO_DISPLAY_NAME = 'SUSE AppCo';
|
|
10
|
+
|
|
11
|
+
// Used when the Rancher version can't be parsed (e.g. dev builds), so we point
|
|
12
|
+
// at the latest, unversioned Fleet docs.
|
|
13
|
+
export const FLEET_DOWNSTREAM_RESOURCES_DOCS_FALLBACK_URL = 'https://fleet.rancher.io/next/downstream-resources';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build the URL to the Fleet "downstream resources" docs for the running Rancher version.
|
|
17
|
+
*
|
|
18
|
+
* Rancher `2.X.0` (for X >= 15) ships with Fleet `0.(X+1)`, whose docs are published at
|
|
19
|
+
* `https://fleet.rancher.io/0.<minor + 1>/downstream-resources`. For anything older or
|
|
20
|
+
* unparseable we fall back to the unversioned `next` docs.
|
|
21
|
+
*/
|
|
22
|
+
export function getDownstreamResourcesDocsUrl(rancherVersion?: string): string {
|
|
23
|
+
// Harcoded to 2.X.0, it is fragile because if the version changes it will break.
|
|
24
|
+
// Ideally it would require a correlation between versions, but we have the fallback in place.
|
|
25
|
+
const match = /^v?2\.(\d+)/.exec(rancherVersion || '');
|
|
26
|
+
const minor = match ? parseInt(match[1], 10) : NaN;
|
|
27
|
+
|
|
28
|
+
// It should only exists after version 0.15.0, which will be fleet 0.16.0.
|
|
29
|
+
if (!isNaN(minor) && minor >= 15) {
|
|
30
|
+
return `https://fleet.rancher.io/0.${ minor + 1 }/downstream-resources`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return FLEET_DOWNSTREAM_RESOURCES_DOCS_FALLBACK_URL;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface AuthCredentials {
|
|
37
|
+
publicKey: string;
|
|
38
|
+
privateKey: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RepoState {
|
|
42
|
+
repoName: string;
|
|
43
|
+
stateDisplay: string;
|
|
44
|
+
stateBackground: string;
|
|
45
|
+
transitioning: boolean;
|
|
46
|
+
error: boolean;
|
|
47
|
+
errorMessage: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface WaitResult {
|
|
51
|
+
repo: any;
|
|
52
|
+
state: RepoState | null;
|
|
53
|
+
// True only when the repo definitively does not exist (404), as opposed to the
|
|
54
|
+
// lookup failing for another reason (network/API error). Distinguishes "repo
|
|
55
|
+
// absent, safe to create" from "couldn't reach the repo".
|
|
56
|
+
notFound?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface VuexStore {
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
+
dispatch: (action: string, payload?: any) => Promise<any>;
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
+
getters: Record<string, any>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function createAppCoAuthSecret(store: VuexStore, credentials: AuthCredentials, namespace: string) {
|
|
67
|
+
const { publicKey, privateKey } = credentials;
|
|
68
|
+
|
|
69
|
+
const secret = await store.dispatch(`${ CATALOG._MANAGEMENT }/create`, {
|
|
70
|
+
type: SECRET,
|
|
71
|
+
metadata: {
|
|
72
|
+
namespace,
|
|
73
|
+
generateName: FLEET_APPCO_AUTH_GENERATE_NAME,
|
|
74
|
+
labels: { [FLEET_LABELS.MANAGED]: 'true' }
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
secret._type = SECRET_TYPES.BASIC;
|
|
79
|
+
secret.data = {
|
|
80
|
+
username: base64Encode(publicKey),
|
|
81
|
+
password: base64Encode(privateKey),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
await secret.save();
|
|
85
|
+
|
|
86
|
+
return secret;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function ensureAppCoImagePullSecret(store: VuexStore, authSecretName: string, namespace: string): Promise<string | undefined> {
|
|
90
|
+
const imagePullSecretName = `${ authSecretName }${ IMAGE_PULL_SECRET_SUFFIX }`;
|
|
91
|
+
|
|
92
|
+
let imagePullSecret = store.getters[`${ CATALOG._MANAGEMENT }/byId`](SECRET, `${ namespace }/${ imagePullSecretName }`);
|
|
93
|
+
|
|
94
|
+
if (!imagePullSecret) {
|
|
95
|
+
try {
|
|
96
|
+
imagePullSecret = await store.dispatch(`${ CATALOG._MANAGEMENT }/find`, { type: SECRET, id: `${ namespace }/${ imagePullSecretName }` });
|
|
97
|
+
} catch (e) {
|
|
98
|
+
let authSecret;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
authSecret = await store.dispatch(`${ CATALOG._MANAGEMENT }/find`, { type: SECRET, id: `${ namespace }/${ authSecretName }` });
|
|
102
|
+
} catch (_) {
|
|
103
|
+
console.warn(`AppCo: auth secret "${ authSecretName }" not found in namespace "${ namespace }", skipping image-pull-secret creation`); // eslint-disable-line no-console
|
|
104
|
+
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const registryHost = new URL(SUSE_APP_COLLECTION_REPO_URL.replace('oci://', 'https://')).host;
|
|
109
|
+
const username = authSecret.decodedData?.username || '';
|
|
110
|
+
const password = authSecret.decodedData?.password || '';
|
|
111
|
+
const config = { auths: { [registryHost]: { username, password } } };
|
|
112
|
+
|
|
113
|
+
const newSecret = await store.dispatch(`${ CATALOG._MANAGEMENT }/create`, {
|
|
114
|
+
type: SECRET,
|
|
115
|
+
_type: SECRET_TYPES.DOCKER_JSON,
|
|
116
|
+
metadata: {
|
|
117
|
+
name: imagePullSecretName,
|
|
118
|
+
namespace,
|
|
119
|
+
labels: { [FLEET_LABELS.MANAGED]: 'true' }
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
newSecret.setData('.dockerconfigjson', JSON.stringify(config));
|
|
124
|
+
await newSecret.save();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return imagePullSecretName;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function ensureAppCoClusterRepo(store: VuexStore, authSecretName: string, namespace: string, t: (key: string) => string): Promise<string> {
|
|
132
|
+
const repoName = deriveRepoName(authSecretName);
|
|
133
|
+
let repo = store.getters[`${ CATALOG._MANAGEMENT }/byId`](CATALOG_TYPES.CLUSTER_REPO, repoName);
|
|
134
|
+
|
|
135
|
+
if (!repo) {
|
|
136
|
+
try {
|
|
137
|
+
repo = await store.dispatch(`${ CATALOG._MANAGEMENT }/find`, { type: CATALOG_TYPES.CLUSTER_REPO, id: repoName });
|
|
138
|
+
} catch (e) {
|
|
139
|
+
try {
|
|
140
|
+
repo = await store.dispatch(`${ CATALOG._MANAGEMENT }/create`, {
|
|
141
|
+
type: CATALOG_TYPES.CLUSTER_REPO,
|
|
142
|
+
metadata: {
|
|
143
|
+
name: repoName,
|
|
144
|
+
annotations: {
|
|
145
|
+
[DESCRIPTION]: t('catalog.repo.target.suseAppCollection.description'),
|
|
146
|
+
[CATALOG.SUSE_APP_COLLECTION]: 'true',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
spec: {
|
|
150
|
+
url: SUSE_APP_COLLECTION_REPO_URL,
|
|
151
|
+
clientSecret: {
|
|
152
|
+
namespace,
|
|
153
|
+
name: authSecretName,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await repo.save();
|
|
159
|
+
} catch (err: any) {
|
|
160
|
+
if (err.status === 409) {
|
|
161
|
+
return repoName;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return repoName;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Verify the auth secret exists, then ensure its image-pull secret and ClusterRepo
|
|
174
|
+
* exist. Returns false (and creates nothing) if the secret cannot be found.
|
|
175
|
+
*/
|
|
176
|
+
export async function ensureAppCoResources(
|
|
177
|
+
store: VuexStore,
|
|
178
|
+
authSecretName: string,
|
|
179
|
+
namespace: string,
|
|
180
|
+
t: (key: string) => string
|
|
181
|
+
): Promise<boolean> {
|
|
182
|
+
const secretId = `${ namespace }/${ authSecretName }`;
|
|
183
|
+
let authSecret = store.getters[`${ CATALOG._MANAGEMENT }/byId`](SECRET, secretId);
|
|
184
|
+
|
|
185
|
+
if (!authSecret) {
|
|
186
|
+
try {
|
|
187
|
+
authSecret = await store.dispatch(`${ CATALOG._MANAGEMENT }/find`, { type: SECRET, id: secretId });
|
|
188
|
+
} catch (e) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
await Promise.all([
|
|
194
|
+
ensureAppCoImagePullSecret(store, authSecretName, namespace),
|
|
195
|
+
ensureAppCoClusterRepo(store, authSecretName, namespace, t),
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const REPO_WAIT_TIMEOUT_MS = 90000;
|
|
202
|
+
const REPO_WAIT_INTERVAL_MS = 3000;
|
|
203
|
+
|
|
204
|
+
function getRepoState(repo: any, repoName: string): { state: RepoState; isReady: boolean; hasError: boolean } {
|
|
205
|
+
const state = repo.metadata?.state;
|
|
206
|
+
const conditions = repo.status?.conditions || [];
|
|
207
|
+
const ociCondition = conditions.find((c: any) => c.type === 'OCIDownloaded');
|
|
208
|
+
const isReady = ociCondition?.status === 'True';
|
|
209
|
+
const hasError = !!(state?.error || ociCondition?.error);
|
|
210
|
+
|
|
211
|
+
const repoState: RepoState = {
|
|
212
|
+
repoName,
|
|
213
|
+
stateDisplay: repo.stateDisplay,
|
|
214
|
+
stateBackground: repo.stateBackground,
|
|
215
|
+
transitioning: !isReady && !hasError,
|
|
216
|
+
error: hasError,
|
|
217
|
+
errorMessage: state?.message || ociCondition?.message || '',
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
state: repoState, isReady, hasError
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function waitForRepoReady(
|
|
226
|
+
store: VuexStore,
|
|
227
|
+
repoName: string,
|
|
228
|
+
{ onStateChange, signal }: { onStateChange?: (state: RepoState) => void; signal?: AbortSignal } = {}
|
|
229
|
+
): Promise<WaitResult> {
|
|
230
|
+
let repo;
|
|
231
|
+
|
|
232
|
+
// `find` with `force: true` re-fetches and registers a watch, so the store's
|
|
233
|
+
// cached resource is kept up to date via subscription while we wait below.
|
|
234
|
+
try {
|
|
235
|
+
repo = await store.dispatch(`${ CATALOG._MANAGEMENT }/find`, {
|
|
236
|
+
type: CATALOG_TYPES.CLUSTER_REPO,
|
|
237
|
+
id: repoName,
|
|
238
|
+
opt: { force: true },
|
|
239
|
+
});
|
|
240
|
+
} catch (e: any) {
|
|
241
|
+
// A 404 means the repo simply doesn't exist yet; any other error means the
|
|
242
|
+
// lookup itself failed (network/API), which callers must not treat as "absent".
|
|
243
|
+
return {
|
|
244
|
+
repo: null, state: null, notFound: e?.status === 404
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
let result: WaitResult = { repo: null, state: null };
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
await repo.waitForTestFn(() => {
|
|
252
|
+
if (signal?.aborted) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Read the latest resource from the store, kept fresh by the watch above.
|
|
257
|
+
const current = store.getters[`${ CATALOG._MANAGEMENT }/byId`](CATALOG_TYPES.CLUSTER_REPO, repoName);
|
|
258
|
+
|
|
259
|
+
if (!current) {
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const { state, isReady, hasError } = getRepoState(current, repoName);
|
|
264
|
+
|
|
265
|
+
onStateChange?.(state);
|
|
266
|
+
|
|
267
|
+
if (hasError) {
|
|
268
|
+
result = { repo: null, state };
|
|
269
|
+
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (isReady) {
|
|
274
|
+
result = { repo: current, state };
|
|
275
|
+
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return false;
|
|
280
|
+
}, `appco repo ${ repoName } ready`, REPO_WAIT_TIMEOUT_MS, REPO_WAIT_INTERVAL_MS);
|
|
281
|
+
} catch (e) {
|
|
282
|
+
// Timed out waiting for the repo to become ready
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (signal?.aborted) {
|
|
287
|
+
return { repo: null, state: null };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
interface FetchChartsResult {
|
|
294
|
+
entries: Record<string, any[]> | null;
|
|
295
|
+
repoState: RepoState | null;
|
|
296
|
+
// True only when the repo does not exist (404); see WaitResult.notFound.
|
|
297
|
+
notFound?: boolean;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export async function fetchAppCoCharts(
|
|
301
|
+
store: VuexStore,
|
|
302
|
+
repoName: string,
|
|
303
|
+
onStateChange?: (state: RepoState) => void,
|
|
304
|
+
// Used to stop on unmount or when repoName changes
|
|
305
|
+
signal?: AbortSignal
|
|
306
|
+
): Promise<FetchChartsResult> {
|
|
307
|
+
const { repo, state: repoState, notFound } = await waitForRepoReady(store, repoName, { onStateChange, signal });
|
|
308
|
+
|
|
309
|
+
if (!repo) {
|
|
310
|
+
return {
|
|
311
|
+
entries: null, repoState, notFound
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const index = await repo.followLink('index');
|
|
316
|
+
const entries = index?.entries || {};
|
|
317
|
+
|
|
318
|
+
return { entries, repoState };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function deriveRepoName(secretName: string): string {
|
|
322
|
+
return secretName ? secretName.replace('auth', 'repo') : '';
|
|
323
|
+
}
|
package/utils/fleet.ts
CHANGED
|
@@ -33,6 +33,13 @@ function conditionIsTrue(conditions: Condition[] | undefined, type: string): boo
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
class Application {
|
|
36
|
+
/**
|
|
37
|
+
* gitrepos/helmops are already restricted to clusters in their own namespace
|
|
38
|
+
*
|
|
39
|
+
* this empty selector means all applicable clusters will be selected
|
|
40
|
+
*/
|
|
41
|
+
includeAllWorkgroupRule = { clusterSelector: { matchExpressions: [] } }
|
|
42
|
+
|
|
36
43
|
excludeHarvesterRule = {
|
|
37
44
|
clusterSelector: {
|
|
38
45
|
matchExpressions: [{
|
|
@@ -45,7 +52,7 @@ class Application {
|
|
|
45
52
|
},
|
|
46
53
|
};
|
|
47
54
|
|
|
48
|
-
getTargetMode(targets: Target[], namespace: string): TargetMode {
|
|
55
|
+
getTargetMode(targets: Target[], namespace: string, areHarvesterHostsVisible: boolean): TargetMode {
|
|
49
56
|
if (namespace === 'fleet-local') {
|
|
50
57
|
return 'local';
|
|
51
58
|
}
|
|
@@ -83,8 +90,11 @@ class Application {
|
|
|
83
90
|
return target;
|
|
84
91
|
});
|
|
85
92
|
|
|
86
|
-
// Check if targets contains only harvester rule after name normalizing
|
|
87
|
-
|
|
93
|
+
// Check if targets contains only harvester rule or no rule at all after name normalizing
|
|
94
|
+
// That means the ALL option has been selected previously
|
|
95
|
+
// one case for feature harvester-baremetal-container-workload ON
|
|
96
|
+
// and another for feature harvester-baremetal-container-workload OFF
|
|
97
|
+
if (isEqual(normalized, [this.includeAllWorkgroupRule]) || isEqual(normalized, [this.excludeHarvesterRule])) {
|
|
88
98
|
mode = 'all';
|
|
89
99
|
}
|
|
90
100
|
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { findTemplateType, findAllConstraintTypes, findAllTemplates, findAllConstraints } from '../util';
|
|
2
|
+
import { GATEKEEPER, SCHEMA } from '@shell/config/types';
|
|
3
|
+
|
|
4
|
+
describe('gatekeeper/util.js', () => {
|
|
5
|
+
describe('findTemplateType', () => {
|
|
6
|
+
it('returns the schema id when a matching schema exists', () => {
|
|
7
|
+
const schemas = [
|
|
8
|
+
{
|
|
9
|
+
id: 'templates.gatekeeper.sh.constrainttemplate',
|
|
10
|
+
attributes: { group: 'templates.gatekeeper.sh', kind: 'ConstraintTemplate' },
|
|
11
|
+
},
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
expect(findTemplateType(schemas)).toStrictEqual('templates.gatekeeper.sh.constrainttemplate');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns undefined when no schemas match', () => {
|
|
18
|
+
const schemas = [
|
|
19
|
+
{
|
|
20
|
+
id: 'other.group.somekind',
|
|
21
|
+
attributes: { group: 'other.group', kind: 'SomeKind' },
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
expect(findTemplateType(schemas)).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('returns undefined for an empty schemas array', () => {
|
|
29
|
+
expect(findTemplateType([])).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('matches only on the correct group and kind together', () => {
|
|
33
|
+
const schemas = [
|
|
34
|
+
{
|
|
35
|
+
id: 'templates.gatekeeper.sh.other',
|
|
36
|
+
attributes: { group: 'templates.gatekeeper.sh', kind: 'Other' },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'other.group.constrainttemplate',
|
|
40
|
+
attributes: { group: 'other.group', kind: 'ConstraintTemplate' },
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
expect(findTemplateType(schemas)).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('returns undefined when schema has no attributes', () => {
|
|
48
|
+
const schemas = [{ id: 'some.id' }];
|
|
49
|
+
|
|
50
|
+
expect(findTemplateType(schemas)).toBeUndefined();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('returns the first matching schema id when multiple matches exist', () => {
|
|
54
|
+
const schemas = [
|
|
55
|
+
{
|
|
56
|
+
id: 'first-match',
|
|
57
|
+
attributes: { group: 'templates.gatekeeper.sh', kind: 'ConstraintTemplate' },
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'second-match',
|
|
61
|
+
attributes: { group: 'templates.gatekeeper.sh', kind: 'ConstraintTemplate' },
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
expect(findTemplateType(schemas)).toStrictEqual('first-match');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('handles schemas with null attributes gracefully', () => {
|
|
69
|
+
const schemas = [
|
|
70
|
+
{ id: 'null-attrs', attributes: null },
|
|
71
|
+
{
|
|
72
|
+
id: 'templates.gatekeeper.sh.constrainttemplate',
|
|
73
|
+
attributes: { group: 'templates.gatekeeper.sh', kind: 'ConstraintTemplate' },
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
expect(findTemplateType(schemas)).toStrictEqual('templates.gatekeeper.sh.constrainttemplate');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('findAllConstraintTypes', () => {
|
|
82
|
+
it('returns constraint type schema ids filtered by constraints.gatekeeper.sh group', () => {
|
|
83
|
+
const schemas = [
|
|
84
|
+
{ id: 'constraints.gatekeeper.sh.k8srequiredlabels', attributes: { group: 'constraints.gatekeeper.sh' } },
|
|
85
|
+
{ id: 'constraints.gatekeeper.sh.k8sallowedrepos', attributes: { group: 'constraints.gatekeeper.sh' } },
|
|
86
|
+
{ id: 'other.group.something', attributes: { group: 'other.group' } },
|
|
87
|
+
];
|
|
88
|
+
const store = { getters: { [`cluster/all`]: () => schemas } };
|
|
89
|
+
|
|
90
|
+
const result = findAllConstraintTypes(store);
|
|
91
|
+
|
|
92
|
+
expect(result).toStrictEqual([
|
|
93
|
+
'constraints.gatekeeper.sh.k8srequiredlabels',
|
|
94
|
+
'constraints.gatekeeper.sh.k8sallowedrepos',
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('returns an empty array when no constraint schemas exist', () => {
|
|
99
|
+
const store = { getters: { [`cluster/all`]: () => [] } };
|
|
100
|
+
|
|
101
|
+
expect(findAllConstraintTypes(store)).toStrictEqual([]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('calls store.getters["cluster/all"] with SCHEMA constant', () => {
|
|
105
|
+
const mockAll = jest.fn().mockReturnValue([]);
|
|
106
|
+
const store = { getters: { [`cluster/all`]: mockAll } };
|
|
107
|
+
|
|
108
|
+
findAllConstraintTypes(store);
|
|
109
|
+
|
|
110
|
+
expect(mockAll).toHaveBeenCalledWith(SCHEMA);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('filters out schemas with missing or non-matching group', () => {
|
|
114
|
+
const schemas = [
|
|
115
|
+
{ id: 'no-attrs' },
|
|
116
|
+
{ id: 'null-group', attributes: { group: null } },
|
|
117
|
+
{ id: 'constraints.gatekeeper.sh.valid', attributes: { group: 'constraints.gatekeeper.sh' } },
|
|
118
|
+
];
|
|
119
|
+
const store = { getters: { [`cluster/all`]: () => schemas } };
|
|
120
|
+
|
|
121
|
+
const result = findAllConstraintTypes(store);
|
|
122
|
+
|
|
123
|
+
expect(result).toStrictEqual(['constraints.gatekeeper.sh.valid']);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('findAllTemplates', () => {
|
|
128
|
+
it('dispatches cluster/findAll with the constraint template type', async() => {
|
|
129
|
+
const expectedResult = [{ id: GATEKEEPER.CONSTRAINT_TEMPLATE }];
|
|
130
|
+
const mockDispatch = jest.fn().mockResolvedValue(expectedResult);
|
|
131
|
+
const store = { dispatch: mockDispatch };
|
|
132
|
+
|
|
133
|
+
const result = await findAllTemplates(store);
|
|
134
|
+
|
|
135
|
+
expect(mockDispatch).toHaveBeenCalledWith('cluster/findAll', { type: GATEKEEPER.CONSTRAINT_TEMPLATE });
|
|
136
|
+
expect(result).toStrictEqual(expectedResult);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('findAllConstraints', () => {
|
|
141
|
+
it('returns all constraint resources flattened across all constraint types', async() => {
|
|
142
|
+
const schemas = [
|
|
143
|
+
{ id: 'constraints.gatekeeper.sh.type1', attributes: { group: 'constraints.gatekeeper.sh' } },
|
|
144
|
+
{ id: 'constraints.gatekeeper.sh.type2', attributes: { group: 'constraints.gatekeeper.sh' } },
|
|
145
|
+
];
|
|
146
|
+
const type1Resources = [{ id: 'r1' }, { id: 'r2' }];
|
|
147
|
+
const type2Resources = [{ id: 'r3' }];
|
|
148
|
+
const mockDispatch = jest.fn()
|
|
149
|
+
.mockResolvedValueOnce(type1Resources)
|
|
150
|
+
.mockResolvedValueOnce(type2Resources);
|
|
151
|
+
const store = {
|
|
152
|
+
getters: { [`cluster/all`]: () => schemas },
|
|
153
|
+
dispatch: mockDispatch,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const result = await findAllConstraints(store);
|
|
157
|
+
|
|
158
|
+
expect(mockDispatch).toHaveBeenCalledWith('cluster/findAll', { type: 'constraints.gatekeeper.sh.type1', opt: { force: true } });
|
|
159
|
+
expect(mockDispatch).toHaveBeenCalledWith('cluster/findAll', { type: 'constraints.gatekeeper.sh.type2', opt: { force: true } });
|
|
160
|
+
expect(result).toStrictEqual([{ id: 'r1' }, { id: 'r2' }, { id: 'r3' }]);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('returns an empty array when there are no constraint types', async() => {
|
|
164
|
+
const store = {
|
|
165
|
+
getters: { [`cluster/all`]: () => [] },
|
|
166
|
+
dispatch: jest.fn(),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result = await findAllConstraints(store);
|
|
170
|
+
|
|
171
|
+
expect(result).toStrictEqual([]);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
describe('gc-interval', () => {
|
|
2
|
+
let gci: any;
|
|
3
|
+
let mockGc: any;
|
|
4
|
+
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
jest.useFakeTimers();
|
|
7
|
+
|
|
8
|
+
mockGc = {
|
|
9
|
+
gcEnabledSetting: jest.fn(),
|
|
10
|
+
gcEnabledInterval: jest.fn(),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
jest.resetModules();
|
|
14
|
+
jest.mock('../gc', () => ({ default: mockGc, __esModule: true }));
|
|
15
|
+
|
|
16
|
+
gci = require('../gc-interval').default;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
jest.useRealTimers();
|
|
21
|
+
jest.restoreAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('gcStartIntervals', () => {
|
|
25
|
+
it.each([
|
|
26
|
+
{
|
|
27
|
+
desc: 'gcEnabledSetting returns false',
|
|
28
|
+
enabledSetting: false,
|
|
29
|
+
enabledInterval: { enabled: true, interval: 60 },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
desc: 'gcEnabledInterval returns enabled=false',
|
|
33
|
+
enabledSetting: true,
|
|
34
|
+
enabledInterval: { enabled: false, interval: 60 },
|
|
35
|
+
},
|
|
36
|
+
])('does not start interval when $desc', ({ enabledSetting, enabledInterval }) => {
|
|
37
|
+
mockGc.gcEnabledSetting.mockReturnValue(enabledSetting);
|
|
38
|
+
mockGc.gcEnabledInterval.mockReturnValue(enabledInterval);
|
|
39
|
+
|
|
40
|
+
const ctx = { dispatch: jest.fn() };
|
|
41
|
+
|
|
42
|
+
gci.gcStartIntervals(ctx);
|
|
43
|
+
|
|
44
|
+
jest.advanceTimersByTime(120_000);
|
|
45
|
+
expect(ctx.dispatch).not.toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('starts interval and dispatches garbageCollect when gc is enabled', () => {
|
|
49
|
+
mockGc.gcEnabledSetting.mockReturnValue(true);
|
|
50
|
+
mockGc.gcEnabledInterval.mockReturnValue({ enabled: true, interval: 60 });
|
|
51
|
+
|
|
52
|
+
const ctx = { dispatch: jest.fn() };
|
|
53
|
+
|
|
54
|
+
gci.gcStartIntervals(ctx);
|
|
55
|
+
|
|
56
|
+
jest.advanceTimersByTime(60_000);
|
|
57
|
+
expect(ctx.dispatch).toHaveBeenCalledWith('garbageCollect');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('dispatches garbageCollect repeatedly according to interval', () => {
|
|
61
|
+
mockGc.gcEnabledSetting.mockReturnValue(true);
|
|
62
|
+
mockGc.gcEnabledInterval.mockReturnValue({ enabled: true, interval: 30 });
|
|
63
|
+
|
|
64
|
+
const ctx = { dispatch: jest.fn() };
|
|
65
|
+
|
|
66
|
+
gci.gcStartIntervals(ctx);
|
|
67
|
+
|
|
68
|
+
jest.advanceTimersByTime(90_000);
|
|
69
|
+
expect(ctx.dispatch).toHaveBeenCalledTimes(3);
|
|
70
|
+
expect(ctx.dispatch).toHaveBeenCalledWith('garbageCollect');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('does not start a second interval if already running', () => {
|
|
74
|
+
mockGc.gcEnabledSetting.mockReturnValue(true);
|
|
75
|
+
mockGc.gcEnabledInterval.mockReturnValue({ enabled: true, interval: 60 });
|
|
76
|
+
|
|
77
|
+
const ctx = { dispatch: jest.fn() };
|
|
78
|
+
|
|
79
|
+
gci.gcStartIntervals(ctx);
|
|
80
|
+
gci.gcStartIntervals(ctx); // second call should be a no-op
|
|
81
|
+
|
|
82
|
+
jest.advanceTimersByTime(60_000);
|
|
83
|
+
expect(ctx.dispatch).toHaveBeenCalledTimes(1);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('gcStopIntervals', () => {
|
|
88
|
+
it('clears the running interval', () => {
|
|
89
|
+
mockGc.gcEnabledSetting.mockReturnValue(true);
|
|
90
|
+
mockGc.gcEnabledInterval.mockReturnValue({ enabled: true, interval: 60 });
|
|
91
|
+
|
|
92
|
+
const ctx = { dispatch: jest.fn() };
|
|
93
|
+
|
|
94
|
+
gci.gcStartIntervals(ctx);
|
|
95
|
+
gci.gcStopIntervals();
|
|
96
|
+
|
|
97
|
+
jest.advanceTimersByTime(120_000);
|
|
98
|
+
expect(ctx.dispatch).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('is safe to call when no interval is running', () => {
|
|
102
|
+
expect(() => gci.gcStopIntervals()).not.toThrow();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('allows a new interval to be started after stopping', () => {
|
|
106
|
+
mockGc.gcEnabledSetting.mockReturnValue(true);
|
|
107
|
+
mockGc.gcEnabledInterval.mockReturnValue({ enabled: true, interval: 60 });
|
|
108
|
+
|
|
109
|
+
const ctx = { dispatch: jest.fn() };
|
|
110
|
+
|
|
111
|
+
gci.gcStartIntervals(ctx);
|
|
112
|
+
gci.gcStopIntervals();
|
|
113
|
+
gci.gcStartIntervals(ctx);
|
|
114
|
+
|
|
115
|
+
jest.advanceTimersByTime(60_000);
|
|
116
|
+
expect(ctx.dispatch).toHaveBeenCalledTimes(1);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|