diff options
Diffstat (limited to 'app/assets/javascripts/environments')
15 files changed, 276 insertions, 133 deletions
diff --git a/app/assets/javascripts/environments/components/edit_environment.vue b/app/assets/javascripts/environments/components/edit_environment.vue index 7905c5cf572..a2405d23924 100644 --- a/app/assets/javascripts/environments/components/edit_environment.vue +++ b/app/assets/javascripts/environments/components/edit_environment.vue @@ -1,10 +1,10 @@ <script> import { GlLoadingIcon } from '@gitlab/ui'; import { createAlert } from '~/alert'; -import axios from '~/lib/utils/axios_utils'; import { visitUrl } from '~/lib/utils/url_utility'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import getEnvironment from '../graphql/queries/environment.query.graphql'; +import getEnvironmentWithNamespace from '../graphql/queries/environment_with_namespace.graphql'; import updateEnvironment from '../graphql/mutations/update_environment.mutation.graphql'; import EnvironmentForm from './environment_form.vue'; @@ -14,72 +14,42 @@ export default { EnvironmentForm, }, mixins: [glFeatureFlagsMixin()], - inject: ['projectEnvironmentsPath', 'updateEnvironmentPath', 'projectPath'], - props: { - environment: { - required: true, - type: Object, - }, - }, + inject: ['projectEnvironmentsPath', 'projectPath', 'environmentName'], apollo: { environment: { - query: getEnvironment, + query() { + return this.glFeatures?.kubernetesNamespaceForEnvironment + ? getEnvironmentWithNamespace + : getEnvironment; + }, variables() { return { - environmentName: this.environment.name, + environmentName: this.environmentName, projectFullPath: this.projectPath, }; }, update(data) { - this.formEnvironment = data?.project?.environment || {}; + const result = data?.project?.environment || {}; + this.formEnvironment = { ...result, clusterAgentId: result?.clusterAgent?.id }; }, }, }, data() { return { - isQueryLoading: false, loading: false, formEnvironment: null, }; }, - mounted() { - if (this.glFeatures?.environmentSettingsToGraphql) { - this.fetchWithGraphql(); - } else { - this.formEnvironment = { - id: this.environment.id, - name: this.environment.name, - externalUrl: this.environment.external_url, - }; - } + computed: { + isQueryLoading() { + return this.$apollo.queries.environment.loading; + }, }, methods: { - async fetchWithGraphql() { - this.$apollo.addSmartQuery('environmentData', { - variables() { - return { environmentName: this.environment.name, projectFullPath: this.projectPath }; - }, - query: getEnvironment, - update(data) { - const result = data?.project?.environment || {}; - this.formEnvironment = { ...result, clusterAgentId: result?.clusterAgent?.id }; - }, - watchLoading: (isLoading) => { - this.isQueryLoading = isLoading; - }, - }); - }, onChange(environment) { this.formEnvironment = environment; }, - onSubmit() { - if (this.glFeatures?.environmentSettingsToGraphql) { - this.updateWithGraphql(); - } else { - this.updateWithAxios(); - } - }, - async updateWithGraphql() { + async onSubmit() { this.loading = true; try { const { data } = await this.$apollo.mutate({ @@ -89,6 +59,7 @@ export default { id: this.formEnvironment.id, externalUrl: this.formEnvironment.externalUrl, clusterAgentId: this.formEnvironment.clusterAgentId, + kubernetesNamespace: this.formEnvironment.kubernetesNamespace, }, }, }); @@ -111,20 +82,6 @@ export default { this.loading = false; } }, - updateWithAxios() { - this.loading = true; - axios - .put(this.updateEnvironmentPath, { - id: this.formEnvironment.id, - external_url: this.formEnvironment.externalUrl, - }) - .then(({ data: { path } }) => visitUrl(path)) - .catch((error) => { - const message = error.response.data.message[0]; - createAlert({ message }); - this.loading = false; - }); - }, }, }; </script> diff --git a/app/assets/javascripts/environments/components/environment_form.vue b/app/assets/javascripts/environments/components/environment_form.vue index 266b221b481..1bff013b9c2 100644 --- a/app/assets/javascripts/environments/components/environment_form.vue +++ b/app/assets/javascripts/environments/components/environment_form.vue @@ -7,6 +7,7 @@ import { GlCollapsibleListbox, GlLink, GlSprintf, + GlAlert, } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { isAbsolute } from '~/lib/utils/url_utility'; @@ -15,7 +16,10 @@ import { ENVIRONMENT_NEW_HELP_TEXT, ENVIRONMENT_EDIT_HELP_TEXT, } from 'ee_else_ce/environments/constants'; +import csrf from '~/lib/utils/csrf'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import getNamespacesQuery from '../graphql/queries/k8s_namespaces.query.graphql'; import getUserAuthorizedAgents from '../graphql/queries/user_authorized_agents.query.graphql'; export default { @@ -27,11 +31,13 @@ export default { GlCollapsibleListbox, GlLink, GlSprintf, + GlAlert, }, mixins: [glFeatureFlagsMixin()], inject: { protectedEnvironmentSettingsPath: { default: '' }, projectPath: { default: '' }, + kasTunnelUrl: { default: '' }, }, props: { environment: { @@ -64,11 +70,13 @@ export default { urlFeedback: __('The URL should start with http:// or https://'), agentLabel: s__('Environments|GitLab agent'), agentHelpText: s__('Environments|Select agent'), + namespaceLabel: s__('Environments|Kubernetes namespace (optional)'), + namespaceHelpText: s__('Environments|Select namespace'), save: __('Save'), cancel: __('Cancel'), reset: __('Reset'), }, - helpPagePath: helpPagePath('ci/environments/index.md'), + environmentsHelpPagePath: helpPagePath('ci/environments/index.md'), renamingDisabledHelpPagePath: helpPagePath('ci/environments/index.md', { anchor: 'rename-an-environment', }), @@ -81,10 +89,41 @@ export default { userAccessAuthorizedAgents: [], loadingAgentsList: false, selectedAgentId: this.environment.clusterAgentId, - searchTerm: '', + agentSearchTerm: '', + selectedNamespace: this.environment.kubernetesNamespace, + k8sNamespaces: [], + namespaceSearchTerm: '', + kubernetesError: '', }; }, + apollo: { + k8sNamespaces: { + query: getNamespacesQuery, + skip() { + return !this.showNamespaceSelector; + }, + variables() { + return { + configuration: this.k8sAccessConfiguration, + }; + }, + update(data) { + return data?.k8sNamespaces || []; + }, + error(error) { + this.kubernetesError = error.message; + }, + result(result) { + if (!result?.error && !result.errors?.length) { + this.kubernetesError = null; + } + }, + }, + }, computed: { + loadingNamespacesList() { + return this.$apollo.queries.k8sNamespaces.loading; + }, isNameDisabled() { return Boolean(this.environment.id); }, @@ -105,7 +144,7 @@ export default { }; }); }, - dropdownToggleText() { + agentDropdownToggleText() { if (!this.selectedAgentId) { return this.$options.i18n.agentHelpText; } @@ -115,13 +154,48 @@ export default { return selectedAgentById?.text || this.environment.clusterAgent?.name; }, filteredAgentsList() { - const lowerCasedSearchTerm = this.searchTerm.toLowerCase(); + const lowerCasedSearchTerm = this.agentSearchTerm.toLowerCase(); return this.agentsList.filter((item) => item.text.toLowerCase().includes(lowerCasedSearchTerm), ); }, - showAgentsSelect() { - return this.glFeatures?.environmentSettingsToGraphql; + namespacesList() { + return this.k8sNamespaces.map((item) => { + return { + value: item.metadata.name, + text: item.metadata.name, + }; + }); + }, + filteredNamespacesList() { + const lowerCasedSearchTerm = this.namespaceSearchTerm.toLowerCase(); + return this.namespacesList.filter((item) => + item.text.toLowerCase().includes(lowerCasedSearchTerm), + ); + }, + isKasKubernetesNamespaceAvailable() { + return this.glFeatures?.kubernetesNamespaceForEnvironment; + }, + showNamespaceSelector() { + return Boolean(this.isKasKubernetesNamespaceAvailable && this.selectedAgentId); + }, + namespaceDropdownToggleText() { + return this.selectedNamespace || this.$options.i18n.namespaceHelpText; + }, + k8sAccessConfiguration() { + if (!this.showNamespaceSelector) { + return null; + } + return { + basePath: this.kasTunnelUrl, + baseOptions: { + headers: { + 'GitLab-Agent-Id': getIdFromGraphQLId(this.selectedAgentId), + ...csrf.headers, + }, + withCredentials: true, + }, + }; }, }, watch: { @@ -151,7 +225,14 @@ export default { }); }, onAgentSearch(search) { - this.searchTerm = search; + this.agentSearchTerm = search; + }, + onAgentChange($event) { + this.selectedNamespace = null; + this.onChange({ ...this.environment, clusterAgentId: $event, kubernetesNamespace: null }); + }, + onNamespaceSearch(search) { + this.namespaceSearchTerm = search; }, }, }; @@ -171,7 +252,9 @@ export default { > <template #link="{ content }"> <gl-link - :href="showEditHelp ? protectedEnvironmentSettingsPath : $options.helpPagePath" + :href=" + showEditHelp ? protectedEnvironmentSettingsPath : $options.environmentsHelpPagePath + " >{{ content }}</gl-link > </template> @@ -223,29 +306,53 @@ export default { /> </gl-form-group> - <gl-form-group - v-if="showAgentsSelect" - :label="$options.i18n.agentLabel" - label-for="environment_agent" - > + <gl-form-group :label="$options.i18n.agentLabel" label-for="environment_agent"> <gl-collapsible-listbox id="environment_agent" v-model="selectedAgentId" class="gl-w-full" + data-testid="agent-selector" block :items="filteredAgentsList" :loading="loadingAgentsList" - :toggle-text="dropdownToggleText" + :toggle-text="agentDropdownToggleText" :header-text="$options.i18n.agentHelpText" :reset-button-label="$options.i18n.reset" :searchable="true" @shown="getAgentsList" @search="onAgentSearch" - @select="onChange({ ...environment, clusterAgentId: $event })" + @select="onAgentChange" @reset="onChange({ ...environment, clusterAgentId: null })" /> </gl-form-group> + <gl-form-group + v-if="showNamespaceSelector" + :label="$options.i18n.namespaceLabel" + label-for="environment_namespace" + > + <gl-alert v-if="kubernetesError" variant="warning" :dismissible="false" class="gl-mb-5"> + {{ kubernetesError }} + </gl-alert> + <gl-collapsible-listbox + v-else + id="environment_namespace" + v-model="selectedNamespace" + class="gl-w-full" + data-testid="namespace-selector" + block + :items="filteredNamespacesList" + :loading="loadingNamespacesList" + :toggle-text="namespaceDropdownToggleText" + :header-text="$options.i18n.namespaceHelpText" + :reset-button-label="$options.i18n.reset" + :searchable="true" + @search="onNamespaceSearch" + @select="onChange({ ...environment, kubernetesNamespace: $event })" + @reset="onChange({ ...environment, kubernetesNamespace: null })" + /> + </gl-form-group> + <div class="gl-mr-6"> <gl-button :loading="loading" diff --git a/app/assets/javascripts/environments/components/new_environment.vue b/app/assets/javascripts/environments/components/new_environment.vue index 3e5f4070066..c6bc94b0b80 100644 --- a/app/assets/javascripts/environments/components/new_environment.vue +++ b/app/assets/javascripts/environments/components/new_environment.vue @@ -1,8 +1,6 @@ <script> import { createAlert } from '~/alert'; -import axios from '~/lib/utils/axios_utils'; import { visitUrl } from '~/lib/utils/url_utility'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import createEnvironment from '../graphql/mutations/create_environment.mutation.graphql'; import EnvironmentForm from './environment_form.vue'; @@ -10,13 +8,13 @@ export default { components: { EnvironmentForm, }, - mixins: [glFeatureFlagsMixin()], inject: ['projectEnvironmentsPath', 'projectPath'], data() { return { environment: { name: '', externalUrl: '', + clusterAgentId: null, }, loading: false, }; @@ -25,14 +23,7 @@ export default { onChange(env) { this.environment = env; }, - onSubmit() { - if (this.glFeatures?.environmentSettingsToGraphql) { - this.createWithGraphql(); - } else { - this.createWithAxios(); - } - }, - async createWithGraphql() { + async onSubmit() { this.loading = true; try { const { data } = await this.$apollo.mutate({ @@ -43,6 +34,7 @@ export default { externalUrl: this.environment.externalUrl, projectPath: this.projectPath, clusterAgentId: this.environment.clusterAgentId, + kubernetesNamespace: this.environment.kubernetesNamespace, }, }, }); @@ -65,20 +57,6 @@ export default { this.loading = false; } }, - createWithAxios() { - this.loading = true; - axios - .post(this.projectEnvironmentsPath, { - name: this.environment.name, - external_url: this.environment.externalUrl, - }) - .then(({ data: { path } }) => visitUrl(path)) - .catch((error) => { - const message = error.response.data.message[0]; - createAlert({ message }); - this.loading = false; - }); - }, }, }; </script> diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue index 1f3d429cc3e..fda1c85f739 100644 --- a/app/assets/javascripts/environments/components/new_environment_item.vue +++ b/app/assets/javascripts/environments/components/new_environment_item.vue @@ -14,6 +14,7 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import isLastDeployment from '../graphql/queries/is_last_deployment.query.graphql'; import getEnvironmentClusterAgent from '../graphql/queries/environment_cluster_agent.query.graphql'; +import getEnvironmentClusterAgentWithNamespace from '../graphql/queries/environment_cluster_agent_with_namespace.query.graphql'; import ExternalUrl from './environment_external_url.vue'; import Actions from './environment_actions.vue'; import StopComponent from './environment_stop.vue'; @@ -82,7 +83,7 @@ export default { tierTooltip: s__('Environment|Deployment tier'), }, data() { - return { visible: false, clusterAgent: null }; + return { visible: false, clusterAgent: null, kubernetesNamespace: '' }; }, computed: { icon() { @@ -164,11 +165,8 @@ export default { rolloutStatus() { return this.environment?.rolloutStatus; }, - isKubernetesOverviewAvailable() { - return this.glFeatures?.kasUserAccessProject; - }, - showKubernetesOverview() { - return Boolean(this.isKubernetesOverviewAvailable && this.clusterAgent); + isKubernetesNamespaceAvailable() { + return this.glFeatures?.kubernetesNamespaceForEnvironment; }, }, methods: { @@ -180,15 +178,20 @@ export default { } }, getClusterAgent() { - if (!this.isKubernetesOverviewAvailable || this.clusterAgent) return; + if (this.clusterAgent) return; this.$apollo.addSmartQuery('environmentClusterAgent', { variables() { return { environmentName: this.environment.name, projectFullPath: this.projectPath }; }, - query: getEnvironmentClusterAgent, + query() { + return this.isKubernetesNamespaceAvailable + ? getEnvironmentClusterAgentWithNamespace + : getEnvironmentClusterAgent; + }, update(data) { this.clusterAgent = data?.project?.environment?.clusterAgent; + this.kubernetesNamespace = data?.project?.environment?.kubernetesNamespace || ''; }, }); }, @@ -368,11 +371,8 @@ export default { </template> </gl-sprintf> </div> - <div v-if="showKubernetesOverview" :class="$options.kubernetesOverviewClasses"> - <kubernetes-overview - :cluster-agent="clusterAgent" - :namespace="environment.kubernetesNamespace" - /> + <div v-if="clusterAgent" :class="$options.kubernetesOverviewClasses"> + <kubernetes-overview :cluster-agent="clusterAgent" :namespace="kubernetesNamespace" /> </div> <div v-if="rolloutStatus" :class="$options.deployBoardClasses"> <deploy-board-wrapper diff --git a/app/assets/javascripts/environments/components/stop_environment_modal.vue b/app/assets/javascripts/environments/components/stop_environment_modal.vue index 95ece2b653e..b583694e154 100644 --- a/app/assets/javascripts/environments/components/stop_environment_modal.vue +++ b/app/assets/javascripts/environments/components/stop_environment_modal.vue @@ -1,10 +1,14 @@ <script> import { GlSprintf, GlTooltipDirective, GlModal } from '@gitlab/ui'; import { __, s__ } from '~/locale'; +import { DOCS_URL_IN_EE_DIR } from 'jh_else_ce/lib/utils/url_utility'; import eventHub from '../event_hub'; import stopEnvironmentMutation from '../graphql/mutations/stop_environment.mutation.graphql'; export default { + yamlDocsLink: `${DOCS_URL_IN_EE_DIR}/ee/ci/yaml/`, + stoppingEnvironmentDocsLink: `${DOCS_URL_IN_EE_DIR}/environments/#stopping-an-environment`, + id: 'stop-environment-modal', name: 'StopEnvironmentModal', @@ -98,18 +102,15 @@ export default { <strong>{{ content }}</strong> </template> <template #ciConfigLink="{ content }"> - <a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer"> + <a :href="$options.yamlDocsLink" target="_blank" rel="noopener noreferrer"> {{ content }}</a > </template> </gl-sprintf> </p> - <a - href="https://docs.gitlab.com/ee/ci/environments/#stopping-an-environment" - target="_blank" - rel="noopener noreferrer" - >{{ s__('Environments|Learn more about stopping environments') }}</a - > + <a :href="$options.stoppingEnvironmentDocsLink" target="_blank" rel="noopener noreferrer">{{ + s__('Environments|Learn more about stopping environments') + }}</a> </div> </gl-modal> </template> diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js index 2b178964c37..dc9481a5429 100644 --- a/app/assets/javascripts/environments/constants.js +++ b/app/assets/javascripts/environments/constants.js @@ -108,3 +108,19 @@ export const PHASE_RUNNING = 'Running'; export const PHASE_PENDING = 'Pending'; export const PHASE_SUCCEEDED = 'Succeeded'; export const PHASE_FAILED = 'Failed'; + +const ERROR_UNAUTHORIZED = 'unauthorized'; +const ERROR_FORBIDDEN = 'forbidden'; +const ERROR_NOT_FOUND = 'not found'; +const ERROR_OTHER = 'other'; + +export const CLUSTER_AGENT_ERROR_MESSAGES = { + [ERROR_UNAUTHORIZED]: s__( + 'Environment|Unauthorized to access the cluster agent from this environment. Check your authentication and try again.', + ), + [ERROR_FORBIDDEN]: s__( + 'Environment|Forbidden to access the cluster agent from this environment.', + ), + [ERROR_NOT_FOUND]: s__('Environment|Cluster agent not found.'), + [ERROR_OTHER]: s__('Environment|There was an error connecting to the cluster agent.'), +}; diff --git a/app/assets/javascripts/environments/edit.js b/app/assets/javascripts/environments/edit.js index b26d96e15bd..3f22b83e618 100644 --- a/app/assets/javascripts/environments/edit.js +++ b/app/assets/javascripts/environments/edit.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import { removeLastSlashInUrlPath } from '~/lib/utils/url_utility'; import EditEnvironment from './components/edit_environment.vue'; import { apolloProvider } from './graphql/client'; @@ -12,10 +13,10 @@ export default (el) => { const { projectEnvironmentsPath, - updateEnvironmentPath, protectedEnvironmentSettingsPath, projectPath, - environment, + environmentName, + kasTunnelUrl, } = el.dataset; return new Vue({ @@ -23,16 +24,13 @@ export default (el) => { apolloProvider: apolloProvider(), provide: { projectEnvironmentsPath, - updateEnvironmentPath, protectedEnvironmentSettingsPath, projectPath, + environmentName, + kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl), }, render(h) { - return h(EditEnvironment, { - props: { - environment: JSON.parse(environment), - }, - }); + return h(EditEnvironment); }, }); }; diff --git a/app/assets/javascripts/environments/graphql/client.js b/app/assets/javascripts/environments/graphql/client.js index 6d06cff06b9..553b06e632f 100644 --- a/app/assets/javascripts/environments/graphql/client.js +++ b/app/assets/javascripts/environments/graphql/client.js @@ -8,6 +8,7 @@ import environmentToStopQuery from './queries/environment_to_stop.query.graphql' import k8sPodsQuery from './queries/k8s_pods.query.graphql'; import k8sServicesQuery from './queries/k8s_services.query.graphql'; import k8sWorkloadsQuery from './queries/k8s_workloads.query.graphql'; +import k8sNamespacesQuery from './queries/k8s_namespaces.query.graphql'; import { resolvers } from './resolvers'; import typeDefs from './typedefs.graphql'; @@ -161,6 +162,14 @@ export const apolloProvider = (endpoint) => { }, }, }); + cache.writeQuery({ + query: k8sNamespacesQuery, + data: { + metadata: { + name: null, + }, + }, + }); return new VueApollo({ defaultClient, }); diff --git a/app/assets/javascripts/environments/graphql/queries/environment_cluster_agent_with_namespace.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_cluster_agent_with_namespace.query.graphql new file mode 100644 index 00000000000..5e72c2dac20 --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/environment_cluster_agent_with_namespace.query.graphql @@ -0,0 +1,20 @@ +query getEnvironmentClusterAgentWithNamespace($projectFullPath: ID!, $environmentName: String) { + project(fullPath: $projectFullPath) { + id + environment(name: $environmentName) { + id + kubernetesNamespace + clusterAgent { + id + name + webPath + tokens { + nodes { + id + lastUsedAt + } + } + } + } + } +} diff --git a/app/assets/javascripts/environments/graphql/queries/environment_with_namespace.graphql b/app/assets/javascripts/environments/graphql/queries/environment_with_namespace.graphql new file mode 100644 index 00000000000..42796f982b6 --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/environment_with_namespace.graphql @@ -0,0 +1,15 @@ +query getEnvironmentWithNamespace($projectFullPath: ID!, $environmentName: String) { + project(fullPath: $projectFullPath) { + id + environment(name: $environmentName) { + id + name + externalUrl + kubernetesNamespace + clusterAgent { + id + name + } + } + } +} diff --git a/app/assets/javascripts/environments/graphql/queries/k8s_namespaces.query.graphql b/app/assets/javascripts/environments/graphql/queries/k8s_namespaces.query.graphql new file mode 100644 index 00000000000..c05d09b6ca2 --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/k8s_namespaces.query.graphql @@ -0,0 +1,7 @@ +query getK8sNamespaces($configuration: LocalConfiguration) { + k8sNamespaces(configuration: $configuration) @client { + metadata { + name + } + } +} diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js index 044e7927606..8cfe44c5a05 100644 --- a/app/assets/javascripts/environments/graphql/resolvers.js +++ b/app/assets/javascripts/environments/graphql/resolvers.js @@ -6,6 +6,7 @@ import { parseIntPagination, normalizeHeaders, } from '~/lib/utils/common_utils'; +import { humanizeClusterErrors } from '../helpers/k8s_integration_helper'; import pollIntervalQuery from './queries/poll_interval.query.graphql'; import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql'; @@ -72,6 +73,11 @@ const mapWorkloadItems = (items, kind) => { }); }; +const handleClusterError = (err) => { + const error = err?.response?.data?.message ? new Error(err.response.data.message) : err; + throw error; +}; + export const resolvers = (endpoint) => ({ Query: { environmentApp(_context, { page, scope, search }, { cache }) { @@ -124,8 +130,7 @@ export const resolvers = (endpoint) => ({ return podsApi .then((res) => res?.data?.items || []) .catch((err) => { - const error = err?.response?.data?.message ? new Error(err.response.data.message) : err; - throw error; + handleClusterError(err); }); }, k8sServices(_, { configuration }) { @@ -148,8 +153,7 @@ export const resolvers = (endpoint) => ({ }); }) .catch((err) => { - const error = err?.response?.data?.message ? new Error(err.response.data.message) : err; - throw error; + handleClusterError(err); }); }, k8sWorkloads(_, { configuration, namespace }) { @@ -206,6 +210,19 @@ export const resolvers = (endpoint) => ({ return summaryList; }); }, + k8sNamespaces(_, { configuration }) { + const coreV1Api = new CoreV1Api(new Configuration(configuration)); + const namespacesApi = coreV1Api.listCoreV1Namespace(); + + return namespacesApi + .then((res) => { + return res?.data?.items || []; + }) + .catch((err) => { + const error = err?.response?.data?.reason || err; + throw new Error(humanizeClusterErrors(error)); + }); + }, }, Mutation: { stopEnvironmentREST(_, { environment }, { client }) { diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql index 7e46385946f..e2c22dda554 100644 --- a/app/assets/javascripts/environments/graphql/typedefs.graphql +++ b/app/assets/javascripts/environments/graphql/typedefs.graphql @@ -160,6 +160,12 @@ type LocalK8sWorkloads { JobList: [localK8sJob] CronJobList: [localK8sCronJob] } +type k8sNamespaceMetadata { + name: String +} +type LocalK8sNamespaces { + metadata: k8sNamespaceMetadata +} extend type Query { environmentApp(page: Int, scope: String): LocalEnvironmentApp diff --git a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js index 45c65c93a91..e49f1451759 100644 --- a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js +++ b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js @@ -1,4 +1,5 @@ import { differenceInSeconds } from '~/lib/utils/datetime_utility'; +import { CLUSTER_AGENT_ERROR_MESSAGES } from '../constants'; export function generateServicePortsString(ports) { if (!ports?.length) return ''; @@ -139,3 +140,9 @@ export function getCronJobsStatuses(items) { ...(ready.length && { ready }), }; } + +export function humanizeClusterErrors(reason) { + const errorReason = reason.toLowerCase(); + const errorMessage = CLUSTER_AGENT_ERROR_MESSAGES[errorReason]; + return errorMessage || CLUSTER_AGENT_ERROR_MESSAGES.other; +} diff --git a/app/assets/javascripts/environments/new.js b/app/assets/javascripts/environments/new.js index 5dd112ac5e6..652085b1f28 100644 --- a/app/assets/javascripts/environments/new.js +++ b/app/assets/javascripts/environments/new.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import { removeLastSlashInUrlPath } from '~/lib/utils/url_utility'; import NewEnvironment from './components/new_environment.vue'; import { apolloProvider } from './graphql/client'; @@ -10,12 +11,16 @@ export default (el) => { return null; } - const { projectEnvironmentsPath, projectPath } = el.dataset; + const { projectEnvironmentsPath, projectPath, kasTunnelUrl } = el.dataset; return new Vue({ el, apolloProvider: apolloProvider(), - provide: { projectEnvironmentsPath, projectPath }, + provide: { + projectEnvironmentsPath, + projectPath, + kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl), + }, render(h) { return h(NewEnvironment); }, |