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 };