@topgroup/diginext
Version:
A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.
284 lines (283 loc) • 13.8 kB
JavaScript
;
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 };