@rancher/shell 3.0.12-rc.3 → 3.0.12-rc.5
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/assets/styles/global/_button.scss +1 -1
- package/assets/styles/global/_layout.scss +4 -0
- package/assets/translations/en-us.yaml +183 -51
- package/assets/translations/zh-hans.yaml +1 -7
- package/chart/monitoring/ClusterSelector.vue +0 -21
- package/chart/monitoring/prometheus/index.vue +6 -3
- package/components/ActionDropdownShell.vue +5 -3
- package/components/ButtonGroup.vue +26 -1
- package/components/CruResource.vue +212 -16
- package/components/ExplorerMembers.vue +8 -4
- package/components/ExplorerProjectsNamespaces.vue +10 -6
- package/components/GrowlManager.vue +4 -0
- package/components/MgmtNodeList.vue +184 -0
- package/components/PromptRestore.vue +93 -32
- package/components/Questions/index.vue +1 -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 +2 -0
- package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
- package/components/ResourceDetail/index.vue +1 -1
- package/components/ResourceList/Masthead.vue +7 -1
- package/components/ResourceList/index.vue +82 -1
- package/components/ResourceTable.vue +1 -0
- package/components/RichTranslation.vue +5 -2
- package/components/Setting.vue +1 -0
- package/components/SortableTable/index.vue +4 -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 -5
- package/components/__tests__/ButtonGroup.test.ts +56 -0
- package/components/__tests__/PromptRestore.test.ts +169 -19
- 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/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
- package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
- package/components/fleet/FleetClusterTargets/index.vue +189 -146
- 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/GitRepoAdvancedTab.vue +1 -0
- package/components/fleet/GitRepoMetadataTab.vue +5 -0
- package/components/fleet/GitRepoTargetTab.vue +0 -2
- package/components/fleet/HelmOpAdvancedTab.vue +19 -53
- package/components/fleet/HelmOpAppCoConfigTab.vue +597 -0
- package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
- package/components/fleet/HelmOpMetadataTab.vue +5 -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 +253 -0
- 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/FileSelector.vue +39 -1
- package/components/form/KeyValue.vue +23 -2
- package/components/form/LabeledSelect.vue +39 -1
- package/components/form/Labels.vue +22 -3
- package/components/form/NameNsDescription.vue +13 -5
- package/components/form/PrivateRegistry.constants.ts +7 -0
- package/components/form/PrivateRegistry.vue +253 -18
- package/components/form/ResourceTabs/index.vue +1 -0
- package/components/form/SelectOrCreateAuthSecret.vue +140 -17
- package/components/form/__tests__/FileSelector.test.ts +23 -0
- package/components/form/__tests__/NameNsDescription.test.ts +75 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
- package/components/formatter/EtcdSnapshotName.vue +73 -0
- package/components/formatter/InternalExternalIP.vue +10 -4
- package/components/formatter/ServiceTargets.vue +26 -7
- package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
- package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
- package/components/nav/Header.vue +12 -1
- package/components/nav/TopLevelMenu.vue +7 -2
- package/components/nav/__tests__/Header.test.ts +15 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
- package/components/templates/default.vue +16 -4
- package/components/templates/home.vue +9 -4
- package/components/templates/plain.vue +9 -4
- 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/config/features.js +1 -0
- package/config/home-links.js +1 -1
- package/config/labels-annotations.js +3 -0
- package/config/product/explorer.js +17 -4
- package/config/product/manager.js +8 -0
- 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 +20 -6
- package/config/secret.ts +10 -0
- package/config/settings.ts +6 -4
- package/config/table-headers.js +3 -4
- package/config/types.js +16 -0
- package/core/plugin-products-base.ts +3 -3
- package/core/plugin-types.ts +83 -30
- package/core/plugin.ts +3 -0
- package/core/types-provisioning.ts +34 -1
- package/core/types.ts +15 -2
- 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/provisioning.cattle.io.cluster.vue +109 -7
- package/detail/workload/index.vue +12 -55
- package/dialog/RotateEncryptionKeyDialog.vue +33 -9
- package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
- package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +206 -0
- package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
- 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 +34 -9
- 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 +128 -95
- 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/compliance.cattle.io.clusterscanprofile.vue +39 -41
- package/edit/fleet.cattle.io.gitrepo.vue +70 -16
- package/edit/fleet.cattle.io.helmop.vue +542 -141
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
- package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
- package/edit/management.cattle.io.user.vue +5 -2
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +89 -11
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
- 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 +16 -10
- package/mixins/__tests__/auth-config.test.ts +90 -0
- package/mixins/__tests__/chart.test.ts +94 -0
- package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
- package/mixins/auth-config.js +7 -0
- package/mixins/chart.js +11 -2
- package/mixins/child-hook.js +12 -6
- package/mixins/create-edit-view/impl.js +5 -3
- package/mixins/resource-fetch-api-pagination.js +21 -1
- 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__/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.node.ts +22 -0
- package/models/__tests__/namespace.test.ts +36 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +205 -0
- package/models/__tests__/secret.test.ts +68 -1
- package/models/__tests__/workload.test.ts +401 -26
- package/models/catalog.cattle.io.clusterrepo.js +28 -4
- package/models/compliance.cattle.io.clusterscan.js +39 -4
- package/models/fleet-application.js +4 -0
- package/models/fleet.cattle.io.helmop.js +20 -1
- package/models/management.cattle.io.cluster.js +39 -5
- package/models/management.cattle.io.node.js +44 -3
- package/models/namespace.js +1 -1
- package/models/pod.js +46 -3
- package/models/provisioning.cattle.io.cluster.js +64 -14
- package/models/rke.cattle.io.etcdsnapshot.js +17 -9
- package/models/secret.js +19 -0
- package/models/workload.js +120 -20
- package/models/workload.service.js +5 -0
- package/package.json +14 -13
- package/pages/about.vue +5 -6
- package/pages/auth/login.vue +0 -35
- package/pages/auth/setup.vue +11 -0
- 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__/index.test.ts +93 -0
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
- package/pages/c/_cluster/apps/charts/chart.vue +2 -1
- package/pages/c/_cluster/apps/charts/index.vue +48 -10
- package/pages/c/_cluster/apps/charts/install.vue +236 -144
- package/pages/c/_cluster/auth/roles/index.vue +5 -4
- 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/uiplugins/__tests__/index.test.ts +96 -0
- package/pages/c/_cluster/uiplugins/index.vue +15 -0
- package/pages/fail-whale.vue +16 -11
- package/pages/home.vue +16 -46
- package/pkg/require-asset.lib.js +25 -0
- package/pkg/vue.config.js +7 -0
- package/plugins/clean-html.d.ts +9 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +177 -0
- package/plugins/dashboard-store/getters.js +0 -1
- package/plugins/dashboard-store/resource-class.js +114 -19
- package/plugins/steve/__tests__/actions.test.ts +212 -0
- package/plugins/steve/actions.js +96 -0
- package/plugins/steve/steve-pagination-utils.ts +1 -1
- package/rancher-components/Accordion/Accordion.vue +53 -9
- package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
- package/rancher-components/Form/Radio/RadioButton.vue +17 -1
- package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
- package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
- package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
- package/rancher-components/RcButton/RcButton.test.ts +103 -0
- package/rancher-components/RcButton/RcButton.vue +94 -15
- package/rancher-components/RcButton/index.ts +1 -1
- package/rancher-components/RcButton/types.ts +3 -0
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
- 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__/features.test.ts +131 -0
- package/store/__tests__/growl.test.ts +374 -0
- package/store/__tests__/modal.test.ts +131 -0
- package/store/__tests__/notifications.test.ts +434 -0
- package/store/__tests__/slideInPanel.test.ts +88 -0
- package/store/__tests__/type-map.utils.test.ts +433 -0
- package/store/catalog.js +57 -0
- package/store/features.js +4 -0
- package/store/plugins.js +7 -4
- package/types/components/buttonGroup.ts +5 -0
- package/types/shell/index.d.ts +166 -70
- package/utils/__tests__/auth.test.ts +273 -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 +37 -1
- package/utils/__tests__/dynamic-importer.test.ts +102 -0
- package/utils/__tests__/fleet-appco.test.ts +312 -0
- package/utils/__tests__/monitoring.test.ts +130 -0
- package/utils/__tests__/object.test.ts +22 -0
- package/utils/__tests__/operation-cr.test.ts +34 -0
- package/utils/__tests__/platform.test.ts +91 -0
- package/utils/__tests__/position.test.ts +237 -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__/time.test.ts +265 -1
- package/utils/__tests__/title.test.ts +47 -0
- package/utils/__tests__/width.test.ts +53 -0
- package/utils/__tests__/window.test.ts +158 -0
- package/utils/__tests__/xccdf.test.ts +126 -6
- 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/object.js +22 -2
- package/utils/operation-cr.js +19 -0
- package/utils/provider.ts +12 -0
- package/utils/require-asset.ts +7 -0
- package/utils/validators/__tests__/container-images.test.ts +104 -0
- package/utils/validators/__tests__/flow-output.test.ts +91 -0
- package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
- package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
- package/utils/validators/__tests__/private-registry.test.ts +27 -15
- package/utils/validators/private-registry.ts +15 -4
- package/utils/xccdf.ts +39 -42
- package/vue.config.js +1 -1
- 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/object.js
CHANGED
|
@@ -163,6 +163,9 @@ returns an object with no key/value pairs (including nested) where the value is:
|
|
|
163
163
|
undefined
|
|
164
164
|
*/
|
|
165
165
|
export function cleanUp(obj) {
|
|
166
|
+
if ( !obj || typeof obj !== 'object') {
|
|
167
|
+
return obj;
|
|
168
|
+
}
|
|
166
169
|
Object.keys(obj).map((key) => {
|
|
167
170
|
const val = obj[key];
|
|
168
171
|
|
|
@@ -267,7 +270,7 @@ export function diff(from, to, preventNull = false) {
|
|
|
267
270
|
}
|
|
268
271
|
|
|
269
272
|
if (preventNull) {
|
|
270
|
-
|
|
273
|
+
// keys that come from "definedKeys" method are strings with "" chars inside... We need to clean them up
|
|
271
274
|
// so that we can access the value of the obj property
|
|
272
275
|
let key = k;
|
|
273
276
|
|
|
@@ -282,7 +285,24 @@ export function diff(from, to, preventNull = false) {
|
|
|
282
285
|
set(out, key, null);
|
|
283
286
|
}
|
|
284
287
|
} else {
|
|
285
|
-
|
|
288
|
+
const parts = splitObjectPath(k);
|
|
289
|
+
|
|
290
|
+
// Skip any missing nested key whose parent path in out is already a
|
|
291
|
+
// non-object. We don't want to attempt to null out the key that appeared
|
|
292
|
+
// in the diff when a pre-defined key
|
|
293
|
+
// (githubConfigSecret.github_token: '') gets updated to
|
|
294
|
+
// (githubConfigSecret: 'preexisting-secret')
|
|
295
|
+
const skip = parts.some((part) => {
|
|
296
|
+
const existingVal = out?.[part];
|
|
297
|
+
|
|
298
|
+
if (existingVal !== undefined && !isObject(existingVal)) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
if (!skip) {
|
|
304
|
+
set(out, k, null);
|
|
305
|
+
}
|
|
286
306
|
}
|
|
287
307
|
}
|
|
288
308
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a day 2 operation CR for imported clusters.
|
|
3
|
+
*
|
|
4
|
+
* @param {Function} dispatch - The Vuex dispatch function
|
|
5
|
+
* @param {string} type - The operation CRD type
|
|
6
|
+
* @param {object} spec - The operation spec
|
|
7
|
+
* @param {string} namespace - The namespace for the operation CR
|
|
8
|
+
* @param {string} namePrefix - The name prefix for the generated name
|
|
9
|
+
* @returns {Promise} The saved resource
|
|
10
|
+
*/
|
|
11
|
+
export async function createOperationCR(dispatch, type, spec, namespace, namePrefix) {
|
|
12
|
+
const resource = await dispatch('management/create', {
|
|
13
|
+
type,
|
|
14
|
+
metadata: { namespace, generateName: `${ namePrefix }-` },
|
|
15
|
+
spec,
|
|
16
|
+
}, { root: true });
|
|
17
|
+
|
|
18
|
+
return resource.save();
|
|
19
|
+
}
|
package/utils/provider.ts
CHANGED
|
@@ -4,6 +4,10 @@ export function getHostedProviders(context: ClusterProvisionerContext) {
|
|
|
4
4
|
return context?.$extension?.getProviders(context)?.filter((p: IClusterProvisioner) => p.group === 'hosted') || [];
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
export function getCAPIProviders(context: ClusterProvisionerContext) {
|
|
8
|
+
return context?.$extension?.getProviders(context)?.filter((p: IClusterProvisioner) => p.group === 'capi') || [];
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
export function isHostedProvider(context: ClusterProvisionerContext, provisioner: string) {
|
|
8
12
|
if (!provisioner) {
|
|
9
13
|
return false;
|
|
@@ -12,3 +16,11 @@ export function isHostedProvider(context: ClusterProvisionerContext, provisioner
|
|
|
12
16
|
|
|
13
17
|
return provisioners.has(provisioner.toLowerCase());
|
|
14
18
|
}
|
|
19
|
+
export function isCAPIProvider(context: ClusterProvisionerContext, provisioner: string) {
|
|
20
|
+
if (!provisioner) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const provisioners = new Set(getCAPIProviders(context).map((p: IClusterProvisioner) => p.id.toLowerCase()));
|
|
24
|
+
|
|
25
|
+
return provisioners.has(provisioner.toLowerCase());
|
|
26
|
+
}
|
package/utils/require-asset.ts
CHANGED
|
@@ -88,6 +88,13 @@ export function requireJson(path: string): object {
|
|
|
88
88
|
return mod.default || mod;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// Expose asset resolvers on window so extension builds (which use a stub
|
|
92
|
+
// instead of require.context) can delegate to the host dashboard at runtime.
|
|
93
|
+
if (typeof window !== 'undefined') {
|
|
94
|
+
(window as any).__shell_requireAsset = requireAsset;
|
|
95
|
+
(window as any).__shell_requireJson = requireJson;
|
|
96
|
+
}
|
|
97
|
+
|
|
91
98
|
// Exported for testing — allows injecting mock contexts
|
|
92
99
|
export function _setContexts(img: WebpackRequireContext | null, json: WebpackRequireContext | null): void {
|
|
93
100
|
imgCtx = img;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { containerImages } from '@shell/utils/validators/container-images';
|
|
2
|
+
|
|
3
|
+
const mockGetters = {
|
|
4
|
+
'i18n/t': (key: string, args?: object) => (args ? `${ key }:${ JSON.stringify(args) }` : key),
|
|
5
|
+
'i18n/exists': () => false,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
describe('validators/container-images', () => {
|
|
9
|
+
describe('containerImages', () => {
|
|
10
|
+
it('adds required error when containers array is empty (regular workload)', () => {
|
|
11
|
+
const errors: string[] = [];
|
|
12
|
+
const spec = { template: { spec: { containers: [] } } };
|
|
13
|
+
|
|
14
|
+
containerImages(spec, mockGetters, errors);
|
|
15
|
+
|
|
16
|
+
expect(errors).toStrictEqual(['validation.required:{"key":"workload.container.titles.containers"}']);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('adds required error when containers is missing (regular workload)', () => {
|
|
20
|
+
const errors: string[] = [];
|
|
21
|
+
const spec = { template: { spec: {} } };
|
|
22
|
+
|
|
23
|
+
containerImages(spec, mockGetters, errors);
|
|
24
|
+
|
|
25
|
+
expect(errors).toStrictEqual(['validation.required:{"key":"workload.container.titles.containers"}']);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('adds no errors when all containers have images (regular workload)', () => {
|
|
29
|
+
const errors: string[] = [];
|
|
30
|
+
const spec = {
|
|
31
|
+
template: {
|
|
32
|
+
spec: {
|
|
33
|
+
containers: [
|
|
34
|
+
{ name: 'web', image: 'nginx:latest' },
|
|
35
|
+
{ name: 'sidecar', image: 'busybox:1.36' },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
containerImages(spec, mockGetters, errors);
|
|
42
|
+
|
|
43
|
+
expect(errors).toStrictEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('adds image error for each container missing an image (regular workload)', () => {
|
|
47
|
+
const errors: string[] = [];
|
|
48
|
+
const spec = {
|
|
49
|
+
template: {
|
|
50
|
+
spec: {
|
|
51
|
+
containers: [
|
|
52
|
+
{ name: 'web' },
|
|
53
|
+
{ name: 'sidecar', image: 'busybox' },
|
|
54
|
+
{ name: 'proxy' },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
containerImages(spec, mockGetters, errors);
|
|
61
|
+
|
|
62
|
+
expect(errors).toStrictEqual([
|
|
63
|
+
'workload.validation.containerImage:{"name":"web"}',
|
|
64
|
+
'workload.validation.containerImage:{"name":"proxy"}',
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('adds required error when containers array is empty (cronjob)', () => {
|
|
69
|
+
const errors: string[] = [];
|
|
70
|
+
const spec = { jobTemplate: { spec: { template: { spec: { containers: [] } } } } };
|
|
71
|
+
|
|
72
|
+
containerImages(spec, mockGetters, errors);
|
|
73
|
+
|
|
74
|
+
expect(errors).toStrictEqual(['validation.required:{"key":"workload.container.titles.containers"}']);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('adds no errors when all containers have images (cronjob)', () => {
|
|
78
|
+
const errors: string[] = [];
|
|
79
|
+
const spec = { jobTemplate: { spec: { template: { spec: { containers: [{ name: 'job', image: 'alpine:3' }] } } } } };
|
|
80
|
+
|
|
81
|
+
containerImages(spec, mockGetters, errors);
|
|
82
|
+
|
|
83
|
+
expect(errors).toStrictEqual([]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('adds image error for container missing image (cronjob)', () => {
|
|
87
|
+
const errors: string[] = [];
|
|
88
|
+
const spec = { jobTemplate: { spec: { template: { spec: { containers: [{ name: 'batch' }] } } } } };
|
|
89
|
+
|
|
90
|
+
containerImages(spec, mockGetters, errors);
|
|
91
|
+
|
|
92
|
+
expect(errors).toStrictEqual(['workload.validation.containerImage:{"name":"batch"}']);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('does not add error when container is null-like in the array', () => {
|
|
96
|
+
const errors: string[] = [];
|
|
97
|
+
const spec = { template: { spec: { containers: [null] } } };
|
|
98
|
+
|
|
99
|
+
containerImages(spec, mockGetters, errors);
|
|
100
|
+
|
|
101
|
+
expect(errors).toStrictEqual([]);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { flowOutput } from '@shell/utils/validators/flow-output';
|
|
2
|
+
|
|
3
|
+
const mockGetters = {
|
|
4
|
+
'i18n/t': (key: string, args?: object) => (args ? `${ key }:${ JSON.stringify(args) }` : key),
|
|
5
|
+
'i18n/exists': () => false,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
describe('validators/flow-output', () => {
|
|
9
|
+
describe('flowOutput', () => {
|
|
10
|
+
describe('when verifyLocal is not in validatorArgs', () => {
|
|
11
|
+
it('adds global error when globalOutputRefs is empty', () => {
|
|
12
|
+
const errors: string[] = [];
|
|
13
|
+
|
|
14
|
+
flowOutput({ globalOutputRefs: [] }, mockGetters, errors, []);
|
|
15
|
+
|
|
16
|
+
expect(errors).toStrictEqual(['validation.flowOutput.global']);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('adds global error when globalOutputRefs is missing', () => {
|
|
20
|
+
const errors: string[] = [];
|
|
21
|
+
|
|
22
|
+
flowOutput({}, mockGetters, errors, []);
|
|
23
|
+
|
|
24
|
+
expect(errors).toStrictEqual(['validation.flowOutput.global']);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('adds no error when globalOutputRefs is non-empty', () => {
|
|
28
|
+
const errors: string[] = [];
|
|
29
|
+
|
|
30
|
+
flowOutput({ globalOutputRefs: ['output-1'] }, mockGetters, errors, []);
|
|
31
|
+
|
|
32
|
+
expect(errors).toStrictEqual([]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('does not check localOutputRefs', () => {
|
|
36
|
+
const errors: string[] = [];
|
|
37
|
+
|
|
38
|
+
flowOutput({ localOutputRefs: ['local-1'], globalOutputRefs: [] }, mockGetters, errors, []);
|
|
39
|
+
|
|
40
|
+
expect(errors).toStrictEqual(['validation.flowOutput.global']);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('when verifyLocal is in validatorArgs', () => {
|
|
45
|
+
it('adds both error when both localOutputRefs and globalOutputRefs are empty', () => {
|
|
46
|
+
const errors: string[] = [];
|
|
47
|
+
|
|
48
|
+
flowOutput({ localOutputRefs: [], globalOutputRefs: [] }, mockGetters, errors, ['verifyLocal']);
|
|
49
|
+
|
|
50
|
+
expect(errors).toStrictEqual(['validation.flowOutput.both']);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('adds both error when both refs are missing', () => {
|
|
54
|
+
const errors: string[] = [];
|
|
55
|
+
|
|
56
|
+
flowOutput({}, mockGetters, errors, ['verifyLocal']);
|
|
57
|
+
|
|
58
|
+
expect(errors).toStrictEqual(['validation.flowOutput.both']);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('adds no error when localOutputRefs is non-empty', () => {
|
|
62
|
+
const errors: string[] = [];
|
|
63
|
+
|
|
64
|
+
flowOutput({ localOutputRefs: ['local-1'], globalOutputRefs: [] }, mockGetters, errors, ['verifyLocal']);
|
|
65
|
+
|
|
66
|
+
expect(errors).toStrictEqual([]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('adds no error when globalOutputRefs is non-empty', () => {
|
|
70
|
+
const errors: string[] = [];
|
|
71
|
+
|
|
72
|
+
flowOutput({ localOutputRefs: [], globalOutputRefs: ['global-1'] }, mockGetters, errors, ['verifyLocal']);
|
|
73
|
+
|
|
74
|
+
expect(errors).toStrictEqual([]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('adds no error when both refs are non-empty', () => {
|
|
78
|
+
const errors: string[] = [];
|
|
79
|
+
|
|
80
|
+
flowOutput(
|
|
81
|
+
{ localOutputRefs: ['local-1'], globalOutputRefs: ['global-1'] },
|
|
82
|
+
mockGetters,
|
|
83
|
+
errors,
|
|
84
|
+
['verifyLocal']
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(errors).toStrictEqual([]);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { logdna } from '@shell/utils/validators/logging-outputs';
|
|
2
|
+
|
|
3
|
+
const mockGetters = {
|
|
4
|
+
'i18n/t': (key: string, args?: object) => (args ? `${ key }:${ JSON.stringify(args) }` : key),
|
|
5
|
+
'i18n/exists': () => false,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
describe('validators/logging-outputs', () => {
|
|
9
|
+
describe('logdna', () => {
|
|
10
|
+
it('adds no error when value is empty object', () => {
|
|
11
|
+
const errors: string[] = [];
|
|
12
|
+
|
|
13
|
+
logdna({}, mockGetters, errors, []);
|
|
14
|
+
|
|
15
|
+
expect(errors).toStrictEqual([]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('adds no error when value is null', () => {
|
|
19
|
+
const errors: string[] = [];
|
|
20
|
+
|
|
21
|
+
logdna(null, mockGetters, errors, []);
|
|
22
|
+
|
|
23
|
+
expect(errors).toStrictEqual([]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('adds no error when value is undefined', () => {
|
|
27
|
+
const errors: string[] = [];
|
|
28
|
+
|
|
29
|
+
logdna(undefined, mockGetters, errors, []);
|
|
30
|
+
|
|
31
|
+
expect(errors).toStrictEqual([]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('adds no error when api_key is present', () => {
|
|
35
|
+
const errors: string[] = [];
|
|
36
|
+
|
|
37
|
+
logdna({ api_key: 'my-secret-key' }, mockGetters, errors, []);
|
|
38
|
+
|
|
39
|
+
expect(errors).toStrictEqual([]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('adds apiKey error when api_key is missing', () => {
|
|
43
|
+
const errors: string[] = [];
|
|
44
|
+
|
|
45
|
+
logdna({ host: 'logs.example.com' }, mockGetters, errors, []);
|
|
46
|
+
|
|
47
|
+
expect(errors).toStrictEqual(['validation.output.logdna.apiKey']);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('adds apiKey error when api_key is empty string', () => {
|
|
51
|
+
const errors: string[] = [];
|
|
52
|
+
|
|
53
|
+
logdna({ api_key: '' }, mockGetters, errors, []);
|
|
54
|
+
|
|
55
|
+
expect(errors).toStrictEqual(['validation.output.logdna.apiKey']);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|