github.com/kyma-project/kyma-environment-broker@v0.0.1/testing/e2e/skr/gardener/client.js (about)

     1  const k8s = require('@kubernetes/client-node');
     2  const {fromBase64, getEnvOrThrow, debug, error} = require('../utils');
     3  
     4  const GARDENER_PROJECT = process.env.KCP_GARDENER_NAMESPACE || 'garden-kyma-dev';
     5  const COMPASS_ID_ANNOTATION_KEY = 'compass.provisioner.kyma-project.io/runtime-id';
     6  
     7  const {
     8    waitForK8sObject,
     9  } = require('../utils');
    10  
    11  class GardenerConfig {
    12    static fromEnv() {
    13      return new GardenerConfig({
    14        kubeconfigPath: getEnvOrThrow('GARDENER_KUBECONFIG'),
    15        // Exception is not necessary below - shootTemplate is not used in all tests
    16        shootTemplate: process.env['GARDENER_SHOOT_TEMPLATE'],
    17      });
    18    }
    19  
    20    static fromString(kubeconfig) {
    21      return new GardenerConfig({
    22        kubeconfig: kubeconfig,
    23      });
    24    }
    25  
    26    constructor(opts) {
    27      opts = opts || {};
    28      this.kubeconfigPath = opts.kubeconfigPath;
    29      this.kubeconfig = opts.kubeconfig;
    30      this.shootTemplate = opts.shootTemplate;
    31    }
    32  }
    33  
    34  class GardenerClient {
    35    constructor(config) {
    36      config = config || {};
    37      const kc = new k8s.KubeConfig();
    38      if (config.kubeconfigPath) {
    39        kc.loadFromFile(config.kubeconfigPath);
    40      } else if (config.kubeconfig) {
    41        kc.loadFromString(config.kubeconfig);
    42      } else {
    43        throw new Error('Unable to create GardenerClient - no kubeconfig');
    44      }
    45  
    46      this.coreV1API = kc.makeApiClient(k8s.CoreV1Api);
    47      this.dynamicAPI = kc.makeApiClient(k8s.KubernetesObjectApi);
    48      this.watch = new k8s.Watch(kc);
    49      this.shootTemplate = config.shootTemplate;
    50    }
    51  
    52    async createShoot(shootName) {
    53      debug(`Creating a K8S cluster in gardener namespace`);
    54      if (!this.shootTemplate) {
    55        error(`No shoot Template`);
    56        return new Error(`no shoot template defined in the Gardener client`);
    57      }
    58  
    59      const data = fromBase64(this.shootTemplate);
    60  
    61      let replaced = data.replace(/<SHOOT>/g, shootName);
    62      replaced = replaced.replace(/<NAMESPACE>/g, GARDENER_PROJECT);
    63  
    64      const shootTemplate = k8s.loadYaml(replaced);
    65  
    66      await this.dynamicAPI.create(shootTemplate)
    67          .catch((err) => {
    68            const message = JSON.stringify(err.body.message);
    69            error(`Got the error with response ${message}`);
    70          });
    71  
    72      await this.waitForShoot(shootName);
    73  
    74      return this.getShoot(shootName);
    75    }
    76  
    77    async deleteShoot(name) {
    78      await this.dynamicAPI.delete({
    79        apiVersion: 'core.gardener.cloud/v1beta1',
    80        kind: 'Shoot',
    81        metadata: {
    82          name: name,
    83          namespace: GARDENER_PROJECT,
    84        },
    85      })
    86          .catch((err) => {
    87            const message = JSON.stringify(err.body.message);
    88            error(`Got the error with response ${message}`);
    89          });
    90    }
    91  
    92    async waitForShoot(shootName, lastOpType = 'Create') {
    93      return waitForK8sObject(
    94          `/apis/core.gardener.cloud/v1beta1/namespaces/${GARDENER_PROJECT}/shoots`, {}, (_type, _apiObj, watchObj) => {
    95            if (watchObj.object.metadata.name != shootName) {
    96              debug(`Skipping object from watch ${watchObj.object.metadata.name} != ${shootName}`);
    97              return false;
    98            }
    99  
   100            debug(`Waiting for ${watchObj.object.metadata.name} shoot`);
   101            const lastOperation = watchObj.object.status.lastOperation;
   102  
   103            debug(`Check object state ${lastOperation.type} == ${lastOpType} && ${lastOperation.state} == 'Succeeded'`);
   104            debug(`Last operation status: ${JSON.stringify(lastOperation)}`);
   105            return lastOperation.type == lastOpType && lastOperation.state == 'Succeeded';
   106          }, 1200 * 1000, 'Waiting for shoot to be ready timeout', this.watch);
   107    }
   108  
   109    async getShoot(shootName) {
   110      debug(`Fetching shoot: ${shootName} from gardener namespace: ${GARDENER_PROJECT}`);
   111  
   112      const secretResp = await this.coreV1API.readNamespacedSecret(
   113          `${shootName}.kubeconfig`,
   114          GARDENER_PROJECT,
   115      );
   116  
   117      const shootResp = await this.dynamicAPI.read({
   118        apiVersion: 'core.gardener.cloud/v1beta1',
   119        kind: 'Shoot',
   120        metadata: {
   121          name: shootName,
   122          namespace: GARDENER_PROJECT,
   123        },
   124      });
   125  
   126      return {
   127        name: shootName,
   128        compassID: shootResp.body.metadata.annotations[COMPASS_ID_ANNOTATION_KEY],
   129        kubeconfig: fromBase64(secretResp.body.data.kubeconfig),
   130        oidcConfig: shootResp.body.spec.kubernetes.kubeAPIServer.oidcConfig,
   131        shootDomain: shootResp.body.spec.dns.domain,
   132        spec: shootResp.body.spec,
   133      };
   134    }
   135  }
   136  
   137  module.exports = {
   138    GardenerConfig,
   139    GardenerClient,
   140  };