Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/environments')
-rw-r--r--app/assets/javascripts/environments/components/edit_environment.vue75
-rw-r--r--app/assets/javascripts/environments/components/environment_form.vue137
-rw-r--r--app/assets/javascripts/environments/components/new_environment.vue28
-rw-r--r--app/assets/javascripts/environments/components/new_environment_item.vue26
-rw-r--r--app/assets/javascripts/environments/components/stop_environment_modal.vue15
-rw-r--r--app/assets/javascripts/environments/constants.js16
-rw-r--r--app/assets/javascripts/environments/edit.js14
-rw-r--r--app/assets/javascripts/environments/graphql/client.js9
-rw-r--r--app/assets/javascripts/environments/graphql/queries/environment_cluster_agent_with_namespace.query.graphql20
-rw-r--r--app/assets/javascripts/environments/graphql/queries/environment_with_namespace.graphql15
-rw-r--r--app/assets/javascripts/environments/graphql/queries/k8s_namespaces.query.graphql7
-rw-r--r--app/assets/javascripts/environments/graphql/resolvers.js25
-rw-r--r--app/assets/javascripts/environments/graphql/typedefs.graphql6
-rw-r--r--app/assets/javascripts/environments/helpers/k8s_integration_helper.js7
-rw-r--r--app/assets/javascripts/environments/new.js9
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);
},