portainer/portainer

View on GitHub
app/kubernetes/__module.js

Summary

Maintainability
F
3 days
Test Coverage
import { EnvironmentStatus } from '@/react/portainer/environments/types';
import { getSelfSubjectAccessReview } from '@/react/kubernetes/namespaces/getSelfSubjectAccessReview';

import { updateAxiosAdapter } from '@/portainer/services/axios';
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
import { CACHE_REFRESH_EVENT, CACHE_DURATION } from '../portainer/services/http-request.helper';
import { cache } from '../portainer/services/axios';

import registriesModule from './registries';
import customTemplateModule from './custom-templates';
import { reactModule } from './react';
import './views/kubernetes.css';

// The angular-cache npm package didn't have exclude options, so implement a custom cache
// with an added check to only cache kubernetes requests
class ExpirationCache {
  constructor() {
    this.store = new Map();
    this.timeout = CACHE_DURATION;
  }

  get(key) {
    return this.store.get(key);
  }

  put(key, val) {
    // only cache requests with 'kubernetes' in the url
    if (key.includes('kubernetes')) {
      this.store.set(key, val);
      // remove it once it's expired
      setTimeout(() => {
        this.remove(key);
      }, this.timeout);
    }
  }

  remove(key) {
    this.store.delete(key);
  }

  removeAll() {
    this.store = new Map();
  }

  delete() {
    // skip because this is standalone, not a part of $cacheFactory
  }
}

angular.module('portainer.kubernetes', ['portainer.app', registriesModule, customTemplateModule, reactModule]).config([
  '$stateRegistryProvider',
  function ($stateRegistryProvider) {
    'use strict';

    const kubernetes = {
      name: 'kubernetes',
      url: '/kubernetes',
      parent: 'endpoint',
      abstract: true,

      onEnter: /* @ngInject */ function onEnter(
        $async,
        $state,
        endpoint,
        KubernetesHealthService,
        KubernetesNamespaceService,
        Notifications,
        StateManager,
        $http,
        Authentication,
        UserService
      ) {
        return $async(async () => {
          // if the user wants to use front end cache for performance, set the angular caching settings
          const userDetails = Authentication.getUserDetails();
          const user = await UserService.user(userDetails.ID);
          updateAxiosAdapter(user.UseCache);
          if (user.UseCache) {
            $http.defaults.cache = new ExpirationCache();
            window.addEventListener(CACHE_REFRESH_EVENT, () => {
              $http.defaults.cache.removeAll();
              cache.store.clear();
            });
          }

          const kubeTypes = [
            PortainerEndpointTypes.KubernetesLocalEnvironment,
            PortainerEndpointTypes.AgentOnKubernetesEnvironment,
            PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment,
          ];

          if (!kubeTypes.includes(endpoint.Type)) {
            $state.go('portainer.home');
            return;
          }
          try {
            if (endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment) {
              //edge
              try {
                await KubernetesHealthService.ping(endpoint.Id);
                endpoint.Status = EnvironmentStatus.Up;
              } catch (e) {
                endpoint.Status = EnvironmentStatus.Down;
              }
            }

            await StateManager.updateEndpointState(endpoint);

            if (endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment && endpoint.Status === EnvironmentStatus.Down) {
              throw new Error('Unable to contact Edge agent, please ensure that the agent is properly running on the remote environment.');
            }

            // use selfsubject access review to check if we can connect to the kubernetes environment
            // because it's gets a fast response, and is accessible to all users
            try {
              await getSelfSubjectAccessReview(endpoint.Id, 'default');
            } catch (e) {
              throw new Error(`The environment named ${endpoint.Name} is unreachable.`);
            }
          } catch (e) {
            let params = {};

            if (endpoint.Type == PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment) {
              params = { redirect: true, environmentId: endpoint.Id, environmentName: endpoint.Name, route: 'kubernetes.dashboard' };
            } else {
              Notifications.error('Failed loading environment', e);
            }
            $state.go('portainer.home', params, { reload: true, inherit: false });
          }
        });
      },
    };

    const helmApplication = {
      name: 'kubernetes.helm',
      url: '/helm/:namespace/:name',
      views: {
        'content@': {
          component: 'kubernetesHelmApplicationView',
        },
      },
      data: {
        docs: '/user/kubernetes/inspect-helm',
      },
    };

    const services = {
      name: 'kubernetes.services',
      url: '/services',
      views: {
        'content@': {
          component: 'kubernetesServicesView',
        },
      },
      data: {
        docs: '/user/kubernetes/networking/services',
      },
    };

    const ingresses = {
      name: 'kubernetes.ingresses',
      url: '/ingresses',
      views: {
        'content@': {
          component: 'kubernetesIngressesView',
        },
      },
      data: {
        docs: '/user/kubernetes/networking/ingresses',
      },
    };

    const ingressesCreate = {
      name: 'kubernetes.ingresses.create',
      url: '/add',
      views: {
        'content@': {
          component: 'kubernetesIngressesCreateView',
        },
      },
      data: {
        docs: '/user/kubernetes/networking/ingresses/add',
      },
    };

    const ingressesEdit = {
      name: 'kubernetes.ingresses.edit',
      url: '/:namespace/:name/edit',
      views: {
        'content@': {
          component: 'kubernetesIngressesCreateView',
        },
      },
    };

    const applications = {
      name: 'kubernetes.applications',
      url: '/applications',
      views: {
        'content@': {
          component: 'kubernetesApplicationsView',
        },
      },
      data: {
        docs: '/user/kubernetes/applications',
      },
    };

    const applicationCreation = {
      name: 'kubernetes.applications.new',
      url: '/new',
      views: {
        'content@': {
          component: 'kubernetesCreateApplicationView',
        },
      },
      data: {
        docs: '/user/kubernetes/applications/add',
      },
    };

    const application = {
      name: 'kubernetes.applications.application',
      url: '/:namespace/:name?resource-type&tab',
      views: {
        'content@': {
          component: 'applicationDetailsView',
        },
      },
      data: {
        docs: '/user/kubernetes/applications/inspect',
      },
    };

    const applicationEdit = {
      name: 'kubernetes.applications.application.edit',
      url: '/edit',
      views: {
        'content@': {
          component: 'kubernetesCreateApplicationView',
        },
      },
      data: {
        docs: '/user/kubernetes/applications/edit',
      },
    };

    const applicationConsole = {
      name: 'kubernetes.applications.application.console',
      url: '/:pod/:container/console',
      views: {
        'content@': {
          component: 'kubernetesConsoleView',
        },
      },
    };

    const applicationLogs = {
      name: 'kubernetes.applications.application.logs',
      url: '/:pod/:container/logs',
      views: {
        'content@': {
          component: 'kubernetesApplicationLogsView',
        },
      },
    };

    const applicationStats = {
      name: 'kubernetes.applications.application.stats',
      url: '/:pod/:container/stats',
      views: {
        'content@': {
          component: 'kubernetesApplicationStatsView',
        },
      },
    };

    const stacks = {
      name: 'kubernetes.stacks',
      url: '/stacks',
      abstract: true,
    };

    const stack = {
      name: 'kubernetes.stacks.stack',
      url: '/:namespace/:name',
      abstract: true,
    };

    const stackLogs = {
      name: 'kubernetes.stacks.stack.logs',
      url: '/logs',
      views: {
        'content@': {
          component: 'kubernetesStackLogsView',
        },
      },
    };

    const configurations = {
      name: 'kubernetes.configurations',
      url: '/configurations?tab',
      views: {
        'content@': {
          component: 'kubernetesConfigMapsAndSecretsView',
        },
      },
      params: {
        tab: null,
      },
      data: {
        docs: '/user/kubernetes/configurations',
      },
    };
    const configmaps = {
      name: 'kubernetes.configmaps',
      url: '/configmaps',
      abstract: true,
      data: {
        docs: '/user/kubernetes/configurations',
      },
    };

    const configMapCreation = {
      name: 'kubernetes.configmaps.new',
      url: '/new',
      views: {
        'content@': {
          component: 'kubernetesCreateConfigMapView',
        },
      },
      data: {
        docs: '/user/kubernetes/configurations/add-configmap',
      },
    };

    const configMap = {
      name: 'kubernetes.configmaps.configmap',
      url: '/:namespace/:name',
      views: {
        'content@': {
          component: 'kubernetesConfigMapView',
        },
      },
    };

    const secrets = {
      name: 'kubernetes.secrets',
      url: '/secrets',
      abstract: true,
      data: {
        docs: '/user/kubernetes/configurations',
      },
    };

    const secretCreation = {
      name: 'kubernetes.secrets.new',
      url: '/new',
      views: {
        'content@': {
          component: 'kubernetesCreateSecretView',
        },
      },
      data: {
        docs: '/user/kubernetes/configurations/add-secret',
      },
    };

    const secret = {
      name: 'kubernetes.secrets.secret',
      url: '/:namespace/:name',
      views: {
        'content@': {
          component: 'kubernetesSecretView',
        },
      },
    };

    const cluster = {
      name: 'kubernetes.cluster',
      url: '/cluster',
      views: {
        'content@': {
          component: 'kubernetesClusterView',
        },
      },
      data: {
        docs: '/user/kubernetes/cluster/details',
      },
    };

    const node = {
      name: 'kubernetes.cluster.node',
      url: '/:nodeName',
      views: {
        'content@': {
          component: 'kubernetesNodeView',
        },
      },
      data: {
        docs: '/user/kubernetes/cluster/node',
      },
    };

    const nodeStats = {
      name: 'kubernetes.cluster.node.stats',
      url: '/stats',
      views: {
        'content@': {
          component: 'kubernetesNodeStatsView',
        },
      },
    };

    const dashboard = {
      name: 'kubernetes.dashboard',
      url: '/dashboard',
      views: {
        'content@': {
          component: 'kubernetesDashboardView',
        },
      },
      data: {
        docs: '/user/kubernetes/dashboard',
      },
    };

    const deploy = {
      name: 'kubernetes.deploy',
      url: '/deploy?templateId&referrer&tab&buildMethod&chartName',
      views: {
        'content@': {
          component: 'kubernetesDeployView',
        },
      },
      data: {
        docs: '/user/kubernetes/applications/manifest',
      },
    };

    const resourcePools = {
      name: 'kubernetes.resourcePools',
      url: '/pools',
      views: {
        'content@': {
          component: 'kubernetesResourcePoolsView',
        },
      },
      data: {
        docs: '/user/kubernetes/namespaces',
      },
    };

    const namespaceCreation = {
      name: 'kubernetes.resourcePools.new',
      url: '/new',
      views: {
        'content@': {
          component: 'kubernetesCreateNamespaceView',
        },
      },
      data: {
        docs: '/user/kubernetes/namespaces/add',
      },
    };

    const resourcePool = {
      name: 'kubernetes.resourcePools.resourcePool',
      url: '/:id',
      views: {
        'content@': {
          component: 'kubernetesResourcePoolView',
        },
      },
      data: {
        docs: '/user/kubernetes/namespaces/manage',
      },
    };

    const resourcePoolAccess = {
      name: 'kubernetes.resourcePools.resourcePool.access',
      url: '/access',
      views: {
        'content@': {
          component: 'kubernetesResourcePoolAccessView',
        },
      },
      data: {
        docs: '/user/kubernetes/namespaces/access',
      },
    };

    const volumes = {
      name: 'kubernetes.volumes',
      url: '/volumes',
      views: {
        'content@': {
          component: 'kubernetesVolumesView',
        },
      },
      data: {
        docs: '/user/kubernetes/volumes',
      },
    };

    const volume = {
      name: 'kubernetes.volumes.volume',
      url: '/:namespace/:name',
      views: {
        'content@': {
          component: 'kubernetesVolumeView',
        },
      },
    };

    const registries = {
      name: 'kubernetes.registries',
      url: '/registries',
      views: {
        'content@': {
          component: 'environmentRegistriesView',
        },
      },
      data: {
        docs: '/user/kubernetes/cluster/registries',
      },
    };

    const registriesAccess = {
      name: 'kubernetes.registries.access',
      url: '/:id/access',
      views: {
        'content@': {
          component: 'kubernetesRegistryAccessView',
        },
      },
    };

    const endpointKubernetesConfiguration = {
      name: 'kubernetes.cluster.setup',
      url: '/configure',
      views: {
        'content@': {
          component: 'kubernetesConfigureView',
        },
      },
      data: {
        docs: '/user/kubernetes/cluster/setup',
      },
    };

    const endpointKubernetesSecurityConstraint = {
      name: 'kubernetes.cluster.securityConstraint',
      url: '/securityConstraint',
      views: {
        'content@': {
          templateUrl: '../kubernetes/views/security-constraint/constraint.html',
          controller: 'KubernetesSecurityConstraintController',
        },
      },
      data: {
        docs: '/user/kubernetes/cluster/security',
      },
    };

    $stateRegistryProvider.register(kubernetes);
    $stateRegistryProvider.register(helmApplication);
    $stateRegistryProvider.register(applications);
    $stateRegistryProvider.register(applicationCreation);
    $stateRegistryProvider.register(application);
    $stateRegistryProvider.register(applicationEdit);
    $stateRegistryProvider.register(applicationConsole);
    $stateRegistryProvider.register(applicationLogs);
    $stateRegistryProvider.register(applicationStats);
    $stateRegistryProvider.register(stacks);
    $stateRegistryProvider.register(stack);
    $stateRegistryProvider.register(stackLogs);
    $stateRegistryProvider.register(configurations);
    $stateRegistryProvider.register(configmaps);
    $stateRegistryProvider.register(configMapCreation);
    $stateRegistryProvider.register(secrets);
    $stateRegistryProvider.register(secretCreation);
    $stateRegistryProvider.register(configMap);
    $stateRegistryProvider.register(secret);
    $stateRegistryProvider.register(cluster);
    $stateRegistryProvider.register(dashboard);
    $stateRegistryProvider.register(deploy);
    $stateRegistryProvider.register(node);
    $stateRegistryProvider.register(nodeStats);
    $stateRegistryProvider.register(resourcePools);
    $stateRegistryProvider.register(namespaceCreation);
    $stateRegistryProvider.register(resourcePool);
    $stateRegistryProvider.register(resourcePoolAccess);
    $stateRegistryProvider.register(volumes);
    $stateRegistryProvider.register(volume);
    $stateRegistryProvider.register(registries);
    $stateRegistryProvider.register(registriesAccess);
    $stateRegistryProvider.register(endpointKubernetesConfiguration);
    $stateRegistryProvider.register(endpointKubernetesSecurityConstraint);

    $stateRegistryProvider.register(services);
    $stateRegistryProvider.register(ingresses);
    $stateRegistryProvider.register(ingressesCreate);
    $stateRegistryProvider.register(ingressesEdit);
  },
]);