UNPKG

@topgroup/diginext

Version:

A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.

284 lines (283 loc) 13.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.execGoogleCloud = exports.showHelp = exports.createImagePullingSecret = exports.connectDockerToRegistry = exports.authenticate = void 0; const chalk_1 = __importDefault(require("chalk")); const log_1 = require("diginext-utils/dist/xconsole/log"); const fs_1 = __importStar(require("fs")); const inquirer_1 = __importDefault(require("inquirer")); const lodash_1 = require("lodash"); const yargs_1 = __importDefault(require("yargs")); const app_config_1 = require("../../app.config"); const config_1 = require("../../config/config"); const plugins_1 = require("../../plugins"); const ask_for_cluster_1 = require("../cluster/ask-for-cluster"); const k8s_1 = __importDefault(require("../k8s")); const kube_config_1 = require("../k8s/kube-config"); /** * Authenticate Google Cloud */ const authenticate = async (options) => { if (!options.filePath) { (0, log_1.logError)(`Param "filePath" is required.`); return false; } const serviceAccountPath = options.filePath; // const containerRegistryUrl = options.host || "asia.gcr.io"; if (!fs_1.default.existsSync(serviceAccountPath)) (0, log_1.logError)(`Service account file not found. Try: "diginext gcloud auth -f /path/to/gcloud-service-account.json"`); try { // authenticate Google Cloud platform with Service Account file await (0, plugins_1.execCmd)(`gcloud auth activate-service-account --key-file ${serviceAccountPath}`); // if success return true; } catch (e) { // if failed (0, log_1.logError)(e); return false; } }; exports.authenticate = authenticate; /** * Connect Docker to Google Cloud Registry */ const connectDockerToRegistry = async (options) => { const { execa, execaCommand, execaSync } = await Promise.resolve().then(() => __importStar(require("execa"))); const { DB } = await Promise.resolve().then(() => __importStar(require("../../modules/api/DB"))); const { host = "https://asia-docker.pkg.dev", filePath, userId, workspaceId, registry: registrySlug, builder = app_config_1.Config.BUILDER } = options; // Validation if (!host) { (0, log_1.logWarn)(`[GCLOUD] You should specify your Google Registry host with`, chalk_1.default.cyan("diginext gcloud registry connect --host"), `<GCP_HOST_URL>`); (0, log_1.logWarn)(`[GCLOUD] Learn more: https://cloud.google.com/container-registry/docs/advanced-authentication`); } // if Service Account (JSON) file is specified as "filePath" (--file / -f) let serviceAccountContent = ""; let serviceAccountObject; if (filePath) { const authRes = await (0, exports.authenticate)({ ...options, filePath }); if (!authRes) { (0, log_1.logError)(`[GCLOUD] Failed to authenticate Google Cloud with service account (json)`); return; } serviceAccountContent = (0, fs_1.readFileSync)(filePath, "utf8"); serviceAccountObject = JSON.parse(serviceAccountContent); } // try { let connectRes; if (builder === "docker") { // connect DOCKER to CONTAINER REGISTRY const subprocess = execa("docker", ["login", "-u", "_json_key", "--password-stdin", host]); (0, fs_1.createReadStream)(filePath).pipe(subprocess.stdin); const { stdout } = await subprocess; connectRes = stdout; // if (host) { // connectRes = await execaCommand(`gcloud auth configure-docker ${host} --quiet`); // } else { // connectRes = await execaCommand(`gcloud auth configure-docker --quiet`); // } } else { // connect PODMAN to CONTAINER REGISTRY connectRes = await execaCommand(`gcloud auth print-access-token | podman login -u oauth2accesstoken --password-stdin ${host || ""}`, { shell: "bash", }); } if (options.isDebugging) (0, log_1.log)(`[GCLOUD] connectDockerRegistry >`, { authRes: connectRes }); } catch (e) { (0, log_1.logError)(`[GCLOUD]`, e); return; } const existingRegistry = await DB.findOne("registry", { slug: registrySlug }, { subpath: "/all", ignorable: true }); if (options.isDebugging) (0, log_1.log)(`[GCLOUD] connectDockerRegistry >`, { existingRegistry }); if (existingRegistry) return existingRegistry; // IF NOT EXISTED -> Save this container registry to database! const registryHost = host || "asia.gcr.io"; const imageBaseURL = `${registryHost}/${serviceAccountObject.project_id}`; const newRegistry = await DB.create("registry", { name: "Google Container Registry", host: registryHost, provider: "gcloud", owner: userId, workspace: workspaceId, imageBaseURL, serviceAccount: serviceAccountContent, }); return newRegistry; }; exports.connectDockerToRegistry = connectDockerToRegistry; /** * Create Google Container Registry image pulling secret */ const createImagePullingSecret = async (options) => { const { execaCommand } = await Promise.resolve().then(() => __importStar(require("execa"))); const { DB } = await Promise.resolve().then(() => __importStar(require("../../modules/api/DB"))); const { registrySlug, namespace = "default", clusterSlug } = options; // log(`providerShortName :>>`, providerShortName); if (!clusterSlug) { (0, log_1.logError)(`Cluster's short name is required.`); return; } // get Container Registry data: const registry = await DB.findOne("registry", { slug: registrySlug }, { subpath: "/all" }); if (!registry) { (0, log_1.logError)(`Container Registry (${registrySlug}) not found. Please contact your admin or create a new one.`); return; } // Get SERVICE ACCOUNT from CONTAINER REGISTRY -> to authenticate & generate "imagePullSecrets" const { host, serviceAccount } = registry; // Get "context" by "cluster" -> to create "imagePullSecrets" of "registry" in cluster's namespace const cluster = await DB.findOne("cluster", { slug: clusterSlug }); if (!cluster) { (0, log_1.logError)(`Can't create "imagePullSecrets" in "${namespace}" namespace of "${clusterSlug}" cluster.`); return; } const { name: context } = await (0, kube_config_1.getKubeContextByCluster)(cluster); // write down the service account file: const serviceAccountPath = (0, plugins_1.createTmpFile)(`gcloud-service-account.json`, serviceAccount); let secretValue; const secretName = `${registrySlug}-docker-registry-key`; // check if namespace is existed const isNsExisted = await k8s_1.default.isNamespaceExisted(namespace, { context }); if (!isNsExisted) { // create new namespace? const ns = await k8s_1.default.createNamespace(namespace, { context }); // still can't create namespace -> throw error! if (!ns) throw new Error(`Namespace "${namespace}" is not existed on this cluster ("${clusterSlug}").`); } // check if the secret is existed within the namespace, try to delete it! const isSecretExisted = await k8s_1.default.isSecretExisted(secretName, namespace, { context }); if (isSecretExisted) await k8s_1.default.deleteSecret(secretName, namespace, { context }); try { const svcAccContentCmd = (0, plugins_1.isWin)() ? `$(type ${serviceAccountPath})` : `$(cat ${serviceAccountPath})`; // Create new "imagePullSecret": const { stdout: newImagePullingSecret } = await execaCommand(`kubectl ${context ? `--context=${context} ` : ""}-n ${namespace} create secret docker-registry ${secretName} --docker-server=${host} --docker-username=_json_key --docker-password="${svcAccContentCmd}" -o json`, (0, plugins_1.isWin)() ? {} : { shell: "bash" }); // delete temporary file // unlink(serviceAccountPath, (err) => err && logError(`[REGISTRY CONTROLLER] Remove tmp file:`, err)); // console.log("GCLOUD > createImagePullingSecret > newImagePullingSecret :>> ", newImagePullingSecret); // create new image pulling secret (in namespace & in database) secretValue = JSON.parse(newImagePullingSecret).data[".dockerconfigjson"]; // log({ secretValue }); // save this secret to database: let updateData = { imagePullSecret: { name: secretName, value: secretValue, }, }; const updatedRegistries = await DB.update("registry", { slug: registrySlug }, updateData); const updatedRegistry = updatedRegistries[0]; if (!app_config_1.isServerMode) { // save registry to local config: (0, config_1.saveCliConfig)({ currentRegistry: updatedRegistry }); } // console.log(JSON.stringify(updatedRegistry.imagePullSecret, null, 2)); // log(`gcloud.createImagePullingSecret() :>>`, { updatedRegistry }); (0, log_1.logSuccess)(`[GCLOUD] ✓ Successfully assign "imagePullSecret" data (${secretName}) to "${namespace}" namespace of "${clusterSlug}" cluster.`); return updatedRegistry.imagePullSecret; } catch (e) { (0, log_1.logError)(`Cannot create image pull secret: ${e}`); return; } }; exports.createImagePullingSecret = createImagePullingSecret; const showHelp = (options) => { (0, log_1.log)(`GCLOUD > Available commands:`); (0, log_1.log)(` diginext gcloud auth`); (0, log_1.log)(` diginext gcloud auth -f`, chalk_1.default.cyan(`/path/to/gcloud-service-account.json`)); (0, log_1.log)(``); (0, log_1.log)(` diginext gcloud registry connect`); (0, log_1.log)(` diginext gcloud registry connect --host`, chalk_1.default.cyan("<GOOGLE_CONTAINER_REGISTRY_HOST>")); (0, log_1.log)(``); (0, log_1.log)(` diginext gcloud registry-secret create -f`, chalk_1.default.cyan(`/path/to/gcloud-service-account.json`), "--namespace", chalk_1.default.cyan("<CLUSTER_NAMESPACE_NAME>")); (0, log_1.log)(``); (0, log_1.log)(`Learn more:`); (0, log_1.log)(` - What is service account: https://cloud.google.com/iam/docs/service-accounts`); (0, log_1.log)(` - Authentication at Google: https://cloud.google.com/docs/authentication#service-accounts`); }; exports.showHelp = showHelp; const execGoogleCloud = async (options) => { const { secondAction } = options; const { DB } = await Promise.resolve().then(() => __importStar(require("../../modules/api/DB"))); switch (secondAction) { case "auth": try { await (0, exports.authenticate)(options); } catch (e) { (0, log_1.logError)(e); } break; case "connect-registry": try { await (0, exports.connectDockerToRegistry)(options); } catch (e) { (0, log_1.logError)(e); } break; case "create-image-pull-secret": const registries = await DB.find("registry", {}, { subpath: "/all" }); if ((0, lodash_1.isEmpty)(registries)) { (0, log_1.logError)(`This workspace doesn't have any registered Container Registries.`); return; } const { selectedRegistry } = await inquirer_1.default.prompt({ message: `Select the container registry:`, type: "list", choices: registries.map((reg, i) => { return { name: `[${i + 1}] ${reg.name} (${reg.provider})`, value: reg }; }), }); const clusterSlug = typeof options.cluster === "boolean" ? (await (0, ask_for_cluster_1.askForCluster)()).slug : options.cluster; try { await (0, exports.createImagePullingSecret)({ clusterSlug, registrySlug: selectedRegistry.slug, namespace: options.namespace, }); } catch (e) { (0, log_1.logError)(e); } break; default: yargs_1.default.showHelp(); break; } }; exports.execGoogleCloud = execGoogleCloud; exports.default = { authenticate: exports.authenticate, connectDockerRegistry: exports.connectDockerToRegistry, createImagePullingSecret: exports.createImagePullingSecret, showHelp: exports.showHelp };