@catladder/cli
Version:
Panter cli tool for cloud CI/CD and DevOps
172 lines (153 loc) • 5.01 kB
text/typescript
import type { ComponentContext } from "@catladder/pipeline";
import {
getFullKubernetesClusterName,
isOfDeployType,
} from "@catladder/pipeline";
import type { CommandInstance } from "vorpal";
import { exec } from "child-process-promise";
import { connectToCluster } from "../../../../../utils/cluster";
import {
doGitlabRequest,
getProjectInfo,
upsertAllVariables,
} from "../../../../../utils/gitlab";
import ensureNamespace from "../utils/ensureNamespace";
export const setupKubernetes = async (
instance: CommandInstance,
context: ComponentContext,
) => {
const deployConfig = context.deploy?.config;
if (!isOfDeployType(deployConfig, "kubernetes")) {
throw new Error("cannot run setupKubernetes on non-kubernetes deployments");
}
const { id: projectId } = await getProjectInfo(instance);
const deploy_tokens = await doGitlabRequest(
instance,
`projects/${projectId}/deploy_tokens`,
);
if (
!deploy_tokens.find(
(v: { name: string }) => v.name === "gitlab-deploy-token",
)
) {
instance.log(
"I will setup the 'GitLab Deploy Token', so Kubernetes can pull images from this project.",
);
await doGitlabRequest(
instance,
`projects/${projectId}/deploy_tokens`,
{
id: projectId,
name: "gitlab-deploy-token",
scopes: ["read_registry"],
},
"POST",
);
}
const fullName = getFullKubernetesClusterName(deployConfig.cluster);
instance.log(`cluster: ${fullName}`);
await connectToCluster(fullName);
instance.log("");
instance.log("ensuring namespace ...");
const namespace = await ensureNamespace(context);
instance.log("Namespace " + namespace + " created / updated!");
instance.log("");
//$.verbose = true;
// we name the service account and the role and the role binding with the same name
// we currently create one per component to better separate them
instance.log("ensuring service accounts...");
const serviceAccountName = `cl-${context.name}-deploy`;
const KUBE_URL = await exec(
`TERM=dumb kubectl cluster-info | grep -E 'Kubernetes master|Kubernetes control plane' | awk '/http/ {print $NF}'`,
).then((s) => s.stdout.trim());
// first upsert service acount in the ns
try {
await exec(
`kubectl delete serviceaccount --namespace ${namespace} ${serviceAccountName}`,
);
await exec(
`kubectl delete rolebinding --namespace ${namespace} ${serviceAccountName}`,
);
await exec(
`kubectl delete role --namespace ${namespace} ${serviceAccountName}`,
);
} catch (e) {
// ignore
}
await exec(
`kubectl create serviceaccount --namespace ${namespace} ${serviceAccountName}`,
);
// upsert role in the ns
await exec(`cat <<EOF | kubectl apply -f -
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: ${namespace}
name: ${serviceAccountName}
rules:
- apiGroups: ["", "extensions", "apps", "networking.k8s.io", "batch", "autoscaling", "rbac.authorization.k8s.io","snapshot.storage.k8s.io"]
resources: ["deployments", "replicasets", "statefulsets", "pods", "secrets", "configmaps", "services", "ingresses", "serviceaccounts", "roles", "rolebindings", "jobs", "cronjobs", "horizontalpodautoscalers", "persistentvolumeclaims", "volumesnapshots"]
verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ${serviceAccountName}
namespace: ${namespace}
subjects:
- kind: ServiceAccount
name: ${serviceAccountName}
namespace: ${namespace}
roleRef:
kind: Role
name: ${serviceAccountName}
apiGroup: rbac.authorization.k8s.io
EOF
`);
const secretName = `${serviceAccountName}-secret`;
// create token for the service account
await exec(`
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: ${secretName}
namespace: ${namespace}
annotations:
kubernetes.io/service-account.name: ${serviceAccountName}
type: kubernetes.io/service-account-token
EOF
`);
const KUBE_CA_PEM = await exec(
`kubectl get secret ${secretName} --namespace ${namespace} -o jsonpath="{['data']['ca\\.crt']}"`,
).then((c) => c.stdout.trim());
const KUBE_TOKEN = await exec(
`kubectl get secret ${secretName} --namespace ${namespace} -o jsonpath="{['data']['token']}" | base64 --decode`,
).then((c) => c.stdout.trim());
const vars = {
KUBE_TOKEN,
KUBE_CA_PEM,
KUBE_URL,
};
const missing = Object.entries(vars).filter((e) => !e[1]);
if (missing.length > 0) {
throw new Error(
"could not setup credentials. Missing vars: " +
missing.map((m) => m[0]).join(", ") +
". Check whether your local kubectl is still working for '" +
fullName +
"'",
);
}
instance.log("service accounts created / updated!");
instance.log("");
instance.log("pusing secrets to gitlab...");
await upsertAllVariables(
instance,
vars,
context.env,
context.name,
false, // no backup
);
instance.log("done!");
};