From 943d44a1d3df443ba0bbb0d7bf28c9c473352c88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathana=C3=ABl=20Blanchet?= Date: Wed, 13 Nov 2024 14:32:14 +0100 Subject: [PATCH] add functions script base --- README.md | 28 +- compose2manifests.sh | 4 +- compose2manifests_functions.sh | 623 +++++++++++++++ .../files/S\303\251lection_529.png" | Bin 0 -> 37645 bytes functions.sh | 715 ++++++++++++++++++ 5 files changed, 1360 insertions(+), 10 deletions(-) create mode 100755 compose2manifests_functions.sh create mode 100644 "documentation/files/S\303\251lection_529.png" create mode 100644 functions.sh diff --git a/README.md b/README.md index 98b9aa1..103d2ca 100644 --- a/README.md +++ b/README.md @@ -23,35 +23,47 @@ Le script [compose2manifests.sh](compose2manifests.sh) a été complété pour r * le support des montages NFS * etc... -L'utilisation et la description des fonctions de ce scripts sont détaillées [ici](documentation/compose2manifests_functions.md). +L'utilisation et la description des fonctions de ce script sont détaillées [ici](documentation/compose2manifests_functions.md). La documentation détaillée relative à l'article et à OKD/Openshift peut être consultée à partir de ce [menu](documentation/README.md). -### Script Bash +### Script Bash compose2manifests.sh Cette procédure ne nécessite qu'un simple fichier docker-compose.yml et du .env correspondant dans le répertoire courant. -Il faut comme prérequis les paquets (la procédure est indépendante de l'OS) +Il faut comme prérequis les paquets (la procédure est indépendante de l'OS GNU/Linux) - jq ```bash -sudo wget https://github.com/jqlang/jq/releases/latest/download/jq-linux-amd64 -O /usr/local/bin/jq && sudo chmod +x /usr/bin/jq +sudo wget https://github.com/jqlang/jq/releases/latest/download/jq-linux-amd64 -O /usr/local/bin/jq && sudo chmod +x /usr/local/bin/jq ``` - yq ```bash -sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq && sudo chmod +x /usr/bin/yq +sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq && sudo chmod +x /usr/local/bin/yq ``` - docker-compose ```bash -sudo wget https://github.com/docker/compose/releases/latest/download/docker-compose-linux-aarch64 -O /usr/local/bin/docker-compose && sudo chmod +x /usr/bin/docker-compose +sudo wget https://github.com/docker/compose/releases/latest/download/docker-compose-linux-aarch64 -O /usr/local/bin/docker-compose && sudo chmod +x /usr/bin/local/docker-compose ``` - kompose ```bash -sudo wget https://github.com/kubernetes/kompose/releases/latest/download/kompose-linux-amd64 -O /usr/local/bin/kompose && sudo chmod +x /usr/bin/kompose +sudo wget https://github.com/kubernetes/kompose/releases/latest/download/kompose-linux-amd64 -O /usr/local/bin/kompose && sudo chmod +x /usr/local/bin/kompose +``` +- jc +```bash +sudo wget -q https://github.com/kellyjonbrazil/jc/releases/download/v1.25.3/jc-1.25.3-linux-x86_64.tar.gz -O /usr/local/bin/ | tar xzf - |chmod +x /usr/local/bin/jc ``` - moreutils ```bash -yum install moreutils -y +# el +dnf config-manager --set-enabled powertools powertools +dnf config-manager --set-enabled powertools crb +dnf install moreutils -y +# debian/ubuntu apt install moreutils -y ``` +L'inventaire des versions logicielles est indiqué en début de script: + +![](documentation/files/Sélection_529.png) + Ensuite il suffit d'exécuter simplement: ```bash chmod +x compose2manifests.sh diff --git a/compose2manifests.sh b/compose2manifests.sh index 680f744..0f25028 100755 --- a/compose2manifests.sh +++ b/compose2manifests.sh @@ -568,7 +568,7 @@ if [ -n "$VARS_TYPE" ] && [ "$VARS_TYPE" == 'env_file' ] done # Déclaration des {services.env} dans docker-compose.yml - title "3.4" "Declaration of \${services}.env into deployments" + title "2.4" "Declaration of \${services}.env into deployments" for i in $(cat $CLEANED|yq eval -ojson|jq -r --arg var "$i" '.services|to_entries|map(select(.value.environment != null)|.key)|flatten[]'); \ do echo $i; cat $CLEANED | \ yq eval - -o json | \ @@ -578,7 +578,7 @@ if [ -n "$VARS_TYPE" ] && [ "$VARS_TYPE" == 'env_file' ] message # Suppression des environnements et nettoyage final - title "3.5" "Cleaning" + title "2.5" "Cleaning" cat $CLEANED \ | jq 'del (.services[].environment)' \ | jq 'del(.networks)' \ diff --git a/compose2manifests_functions.sh b/compose2manifests_functions.sh new file mode 100755 index 0000000..57438cd --- /dev/null +++ b/compose2manifests_functions.sh @@ -0,0 +1,623 @@ +#!/bin/bash +# 24/10/21 blanchetabes.fr +# Script de conversion d'un fichier docker-compose.yaml en manifests k8s +# Génère pour chacun des services ces manifest: deploy, services, configMap, secret, persistentVolumeClaim +# Nécessite les paquets jq, yq, jc, moreutils, docker-compose, kompose +# Usage: +# ./compose2manifests.sh [ prod || test || dev || local ] [ appli_name ] [default || '' || secret || env_file | help] [kompose] [helm]\n" + +source functions.sh + +ENV=$1 +NAME=$2 +VARS_TYPE=$3 +KOMPOSE=$4 +HELM=$5 + +# RED="31" +# GREEN="32" +MAGENTA="\e[35m" +GREEN="\e[92m" +YELLOW="\e[93m" +BLUE="\e[94m" +RED="\e[31m" +CYAN="\e[36m" +BOLDGREEN="\e[1;${GREEN}m" +ITALICRED="\e[3;${RED}m" +ENDCOLOR="\e[0m" +FAINT="\e[2m" +BOLD="\e[1m" +ITALICS="\e[3m" + +case $NAME in + '' | help | --help) + help;; + *) + ;; +esac + +case $VARS_TYPE in + default | '' | secret | env_file) + ;; + *) + help;; +esac + + +step "1" "Project Initialization........" + +title "1.1" "Cleaning working dir" +if [ -f ./okd ] + then rm -rf okd +fi + +# Check potential previous existent sshfs mount +sshfs=$( mount | grep sshfs ) +if [ -n "$sshfs" ] + then + echo -e "There is one active sshfs mount, please unmount it before going on: \n" + blue "$sshfs" + exit 1 +fi +shopt -s extglob +# rm -rf !(.env|docker-compose.yml|*.sh|.git|.|..) +rm -rf !(.env|docker-compose.yml|*.sh|.git|*.md|*.py|documentation|.|..) +message + +if [ "$VARS_TYPE" = "clean" ]; then + echo "Cleaned Wordir"; + exit; +fi + +echo -e "" + +# echo "1.2> #################### Installation des pré-requis ####################" +title "1.2" "Installation of pre-required features" + + +for i in jq yq docker-compose kompose oc jc; do install_bin $i; done + +echo -ne "Application to deploy: " +blue \"$NAME\" +namespace=$(oc config view --minify -o 'jsonpath={..namespace}') +api=$(oc config view --minify -o 'jsonpath={..server}') +echo -ne "Cluster k8s: " +blue $api +echo -ne "Namespace in use: " +blue "\"$namespace\"\n" +case $1 in + test|dev|prod) + echo -e "You will deploy appli $(blue \"$NAME\") from the Docker $(blue \"$1\") platform to\n$(blue \"$api\") k8s cluster in the $(blue \"$namespace\") namespace.\"" + ;; + local) + echo -e "You will deploy appli $(blue \"$NAME\") from a docker-compose.yml file and a .env file (provided by yourself) to $(blue \"$api\") k8s cluster in the $(blue \"$namespace\") namespace." + ;; + *) + echo "Bad arguments" + exit;; +esac + +if [[ $(echo $namespace | grep $NAME > /dev/null && echo $?) != 0 ]]; + then + echo -e "${YELLOW}!! Warning !! ${ENDCOLOR}: current OKD namespace $(blue \"$namespace\") may not correspond to the appli $(blue \"$NAME\") you are about to deploy.\n" + read -p "$(italics "?? Enter the name of a new namespace relative to $(blue \"$NAME\")"): " namespace + blue "$namespace" +fi +echo "################################################################################################################################" +echo -e "" + + +# Check ssh key presence +set_ssh_key + +# Docker hosts domain identification +dom=$(hostname -d) +read -p "$(italics "?? Please enter the domain (default is the one of the bastion) $(faint [$dom]): ")" domain2 +domain=${domain:-$dom} +blue $domain + +echo "" + + +if [[ "$ENV" == "prod" ]] || [[ "$ENV" == "test" ]] || [[ "$ENV" == "dev" ]]; then + echo -e "" + + echo "Ok, let's go on!" + + title "1.3" "Docker host search" + get_running_docker + + read -p "$(italics "?? Choose docker-compose.yml method $(faint "[docker_host)|github]"): ")" method + method=${method:-docker_host} + blue "$method" + case $method in + github ) + italics "Fetching \"docker-compose.yml\" from GitHub......................................."; \ + read -p "$(italics "?? Enter DockerHub URL docker-compose.yml path"): " path + path=${path:-https://raw.githubusercontent.com/abes-esr/$NAME-docker/develop/docker-compose.yml} + wget -N $path 2> /dev/null; \ + fetch "docker-compose.yml" + echo "";; + docker_host ) + echo "Fetching \"docker-compose.yml\" from $docker_host ......................................."; \ + read -p "$(italics "?? Enter docker-compose.yml path on host \"$docker_host\" $(faint [/opt/pod/$NAME-docker]): ")" path + path=${path:-/opt/pod/$NAME-docker} + rsync -a root@$docker_host:$path/docker-compose.yml . ; \ + fetch "docker-compose.yml" + echo "";; + esac + + echo "Fetching \".env\" from $docker_host Docker host..........................................." + rsync -a root@$docker_host:/opt/pod/$NAME-docker/.env .; \ + fetch ".env" + echo "" + +elif [[ "$ENV" == local ]];then + get_running_docker + if ! [[ -f ../docker-compose.yml ]]; then + echo "There is no current docker-compose.yml file for \"$NAME\" in $(pwd)" + read -p "$(italics "?? Choose docker-compose.yml method $(faint "[docker_host|gitlab|manual]"): ")" method + method=${method:-docker_host} + case $method in + gitlab ) + read -p "$(italics "?? If $NAME is hosted on gitlab.abes.fr, you can download your docker-compose.yml $(faint "(y/n)").......................................?")" rep + if [[ "$rep" == "y" ]];then + read -p "$(italics "?? Please provide your gitlab private token (leave empty if public repo):....................................... ")" token + ID=$(curl -s --header "PRIVATE-TOKEN: $token" https://git.abes.fr/api/v4/projects | \ + jq -r --arg name "$NAME" '.[] | select(.name==$name)| .id') + curl -s --header "PRIVATE-TOKEN: $token" https://git.abes.fr/api/v4/projects/${ID}/repository/files/docker-compose.yml/raw?ref=main > docker-compose.yml + vi docker-compose.yml + else + echo "Can't continue... You may previously manually copy your \"docker-compose.yml\" and \".env\" file into $pwd" + exit 1 + fi ;; + docker_host ) + echo "Fetching \"docker-compose.yml\" from $docker_host ......................................."; \ + read -p "$(italics "?? Enter docker-compose.yml path on host \"$docker_host\" $(faint "[/opt/pod/$NAME-docker]"): ")" path + path=${path:-/opt/pod/$NAME-docker} + rsync -a root@$docker_host:$path/docker-compose.yml . ; \ + fetch "docker-compose.yml" + echo "";; + manual ) + echo "Please manually copy the docker-compose.yml to $PWD and re-execute this script" + exit;; + esac + else + cat ../docker-compose.yml |egrep -i "^$NAME$" + if [ "$?" == 0 ]; + then + echo "\"docker-compose.yml\" is already present and ready to be used for \"$NAME\".... " + else + echo "\"docker-compose.yml\" is already present but doesn't seem to belong to \"$NAME\".... " + read -p "$(italics "?? Do you want to continue anyway? $(faint "[n]")")" yn + if [ "$yn" == "n" ]; then echo -e "Please check \"$(cd .. && pwd)/docker-compose.yml\" content.\nExiting" ; exit; fi + + fi + fi + + if ! [[ -f ../.env ]];then + echo "Choose \".env\" method [docker_host|manual]: " method + method=${method:-docker_host} + case $method in + docker_host ) + echo "Fetching \".env\" from $docker_host ......................................."; \ + read -p "$(italics "?? Enter docker-compose.yml path on host \"$docker_host\" $(faint "[/opt/pod/$NAME-docker]]"): ")" path + path=${path:-/opt/pod/$NAME-docker} + rsync -a root@$docker_host:$path/.env . ; \ + fetch ".env" + echo "";; + manual ) + echo "Please manually provide a valid \".env\" file in the same directory as docker-compose.yml file ($PWD)" + exit 1;; + esac + else + cat ../.env |egrep -i "^$NAME$" + if [ "$?" == 0 ]; + then + echo "\".env\" is already present and ready to be used for \"$NAME\".... " + else + echo "\".env\" is already present but doesn't seem to belong to \"$NAME\".... " + read -p "$(italics "?? Do you want to continue anyway? $(faint "[n]")")" yn + if [ "$yn" == "n" ]; then echo -e "Please check \"$(cd .. && pwd)/.env\" content. \nExiting..." ; exit; fi + fi + fi + +elif [[ "$ENV" != "local" ]]; then + echo "Valid verbs are 'github' or 'local'" + exit 1; +elif ! test -f .env || ! test -f docker-compose.yml; then + echo -e "No valid files have been found\nCopy your '.env' and your 'docker-compose.yml in $PWD'"; + exit 1; +fi + +echo "" +# Customizing .env +if test -f .env; + then + read -p "$(italics "?? Do you want to customize your variable environment before the conversion to manifests?: $(faint "[n]") ")" yn + yn=${yn:-n} + while true; do + case $yn in + [Yy]* ) + vi .env + break;; + [Nn]* ) + break;; + esac + done +fi + +echo -e "\n" + +step "2" "Conversion des variables en objet secrets et configMaps" + +title "2.1" ".env resolution" +docker-compose config --format json | yq -o json \ + | jq '.services + |=with_entries( + .key=( + if .value|has("container_name") + then .value."container_name" + else . + end) + )' \ + | yq -P \ + | sed 's/\.svc//g'> $NAME.yml +message + +title "2.2" "Cleaning of $NAME.yml" + +cat $NAME.yml \ + | yq -ojson \ + | jq --arg name "$NAME" 'del (.services."\($name)-watchtower") + | del(..|nulls) + | del(.services[].volumes[]?|select(.source|test("sock"))) + | del(.services[]."depends_on") + | del(.services."theses-elasticsearch-setupcerts") + | del(.services."theses-elasticsearch-setupusers") + | del(.services."theses-api-diffusion-poc") + | (if has("volumes") then .volumes|=with_entries(.key|=gsub("\\.";"-")) else . end) + | .services[].volumes[]?|=(if .type=="bind" then . else .source|=gsub("\\.";"-") end) + | .services|=with_entries(.value|=(select(has("volumes")).volumes |= sort_by((.type)) ))' \ + | yq -P | sponge $NAME.yml + +message + +#### insertion de la clé "secrets" dans chacun des services de docker-compose.yml +#### prend en paramètre le nom du fichier docker-compose.yml + +CLEANED="$NAME.yml" + +if [ -n "$VARS_TYPE" ] && [ "$VARS_TYPE" == 'env_file' ] + then + echo -e "on continue......................................." + SMALL_LIST=$(cat $CLEANED | yq eval - -o json | jq '.services|to_entries[] | {(.key): .value.environment}'| jq -s) + message + + ###### select variable name filtering by KEY or PASSWORD #### + FILTER_LIST=$(echo $SMALL_LIST | yq eval - -o json \ + | jq '.[]|to_entries[]|try {key:.key,value:.value|to_entries[]} | select(.value.key | test("KEY|PASS|SECRET"))' \ + | jq -s ) + message + + ###### Generating secret files from .env file ##### + export var=$(echo $FILTER_LIST | jq -r '.[].value.key') + for i in $(echo $var); \ + do + export data=$(echo $FILTER_LIST | jq --arg tata "$i" -r '[.[].value | select(.key==$tata).value]|first') + echo $data > $(echo $i| sed 's/_/-/g' | tr '[:upper:]' '[:lower:]').txt; \ + cat $CLEANED \ + | yq eval - -o json \ + | jq --arg i $i '.secrets[$i|ascii_downcase|gsub("_";"-")].file = ($i|ascii_downcase|gsub("_";"-")) + ".txt"' \ + | yq eval - -P \ + | sponge $CLEANED; \ + done + message + + ########### Conversion du environment en env_file ############ + + # Génération des {service}.env à partir du docker-compose.yml + title "2.3" "Generation of \${services}.env" + for i in $(cat $CLEANED|yq eval -ojson|jq -r --arg var "$i" '.services|to_entries|map(select(.value.environment != null)|.key)|flatten[]'); \ + do cat $CLEANED | \ + yq eval - -o json |\ + jq -r --arg var "$i" '.services[$var].environment' | \ + # egrep -v 'KEY|PASS|SECRET' | \ + yq eval - -P| \ + sed "s/:\ /=/g" > $i.env; + done + message + + # Déclaration des variables contenant un secret dans le env_file + for i in $(ls *.env); + do + for j in $(cat $i | egrep '(KEY|PASS|SECRET)'); + do + KEY=$(echo $j | cut -d"=" -f1); + LINE=$(echo $KEY | cut -d"=" -f1 | tr '[:upper:]' '[:lower:]' | sed 's/_/-/g'); + sed -i "s/.*$KEY.*/$KEY=\/run\/secrets\/$LINE/g" $i; + done; + done + + # Déclaration des {services.env} dans docker-compose.yml + title "2.4" "Declaration of \${services}.env into deployments" + for i in $(cat $CLEANED|yq eval -ojson|jq -r --arg var "$i" '.services|to_entries|map(select(.value.environment != null)|.key)|flatten[]'); \ + do echo $i; cat $CLEANED | \ + yq eval - -o json | \ + jq -r --arg var "$i" '.services[$var]."env_file" = $var +".env"' | \ + sponge $CLEANED ; \ + done + message + + # Suppression des environnements et nettoyage final + title "2.5" "Cleaning" + cat $CLEANED \ + | jq 'del (.services[].environment)' \ + | jq 'del(.networks)' \ + | jq 'del(.services[].networks)' \ + | jq 'del(.services[].labels."com.centurylinklabs.watchtower.scope")' \ + | yq eval - -P | sponge $CLEANED + +message + # fi + + else + +# Suppression des environnements et nettoyage final +echo -e "5> #################### Suppression des environnements et nettoyage final ####################\n" +cat $CLEANED \ +| yq eval -o json \ +| jq 'del(.networks)' \ +| jq 'del(.services[].networks)' \ +| jq 'del(.services[].labels."com.centurylinklabs.watchtower.scope")' \ +| yq eval - -P | sponge $CLEANED + +message +fi + +#Génération des manifests +step "3" "Docker-compose.yml conversion into Kubernetes manifests with the Kompose tool" + + +title "3.1" "Creating missing network manifests" +services=$(cat $CLEANED | yq -o json| jq -r '.services|to_entries[]|.value|select((has("ports") or has("expose"))|not)?|."container_name"') +if [[ -n $services ]] + then + echo -e "The following services don't have any explicit defined ports: \n$services" + read -p "$(italics "?? Do you want to fetch ports from existing docker containers on $docker_host : $(faint "[y]")")" yn + yn=${yn:-y} + while true; do + case $yn in + [Yy]* ) + patch_expose_auto + break;; + [Nn]* ) + patch_expose + break;; + esac + done +fi + +read -p "$(italics "?? Is this correct to be deployed? $(faint "[y]")............: ")" yn +yn=${yn:-y} +case $yn in + [Yy]* ) blue "Converting \"docker-compose.yml\" into k8s manifests..." + sleep 2;; + * ) blue "Bye" + exit;; +esac + +applis_svc=$(cat $NAME.yml | yq eval -ojson | \ + jq -r '.services|to_entries[]| [{services: .key, volumes: .value.volumes[]|select(.source|test("/appli"))}]?|.[].services'|uniq) +applis_source=$(cat $NAME.yml | yq eval -ojson | \ + jq -r '.services|to_entries[]| [{services: .key, volumes: .value.volumes[]|select(.source|test("/appli"))}]?|.[].volumes.source') +if [ -n "$KOMPOSE" ] && [ "$KOMPOSE" = "kompose" ]; then + title "3.2" "Generation of manifests with Kompose" + if [ -n "$HELM" ] && [ "$HELM" = "helm" ]; then + kompose -f $CLEANED convert -c + cd $NAME/templates + else + kompose -f $CLEANED convert + fi + patch_RWO + title "3.2.1" "Patching secret manifests" + patch_secret + patch_secretKeys + title "3.2.2" "Patching network manifests" + patch_networkPolicy $ENV + title "3.2.3" "Patching storage manifests" + create_pv2 $ENV $NAME + title "3.2.4" "Patching file manifests" + patch_configmaps $CLEANED + patch_labels + # Création des object configMaps pour les volumes bind qui sont des fichiers et non des répertoires" + create_configmaps $ENV +fi + +title "3.3" "Patching multi-attached volumes" +# find targeted volumes +export volumes=$(cat $CLEANED | yq eval -o json | jq -r '.services|to_entries[]|.value|select(has("volumes"))|.volumes[]|select((.type)=="volume").source'|uniq) + +if [ -z "$volumes" ]; then + blue "No multi attached pvc found"; +fi + +# find if there is a nfs csi driver installed on the cluster +export nfs_csi=$(oc get csidrivers.storage.k8s.io -o json | jq -r '.items[].metadata|select(.name|test("nfs")).name') +export nfs_sc=$(oc get sc -o json | jq --arg nfs_csi $nfs_csi -r '.items[]|select(.provisioner|test("\($nfs_csi)")).metadata.name') + + +# find multi attached volume +for i in $volumes + do number=$(cat $CLEANED | yq eval -o json | jq --arg i $i -r '.services|to_entries[]|.value|select(has("volumes"))|.volumes[]|=select((.type)=="volume")| {(.container_name): (.volumes[]|select(.type=="volume"))}|to_entries[]|select(.value.source==$i).key'|wc -l) + if test $number -gt 1 + then blue "There are multi attachments for \"$i\" volume" + echo "A RWX csi driver is required for multi-attachments PVC" + if [[ -n "$nfs_csi" ]] + then + echo "\"$nfs_csi\" driver is installed to this k8s cluster" + if [[ -n $nfs_sc ]]; + then + echo "There is an existing storage class $nfs_sc that points to" + oc get sc -o json | jq --arg nfs_csi $nfs_csi -r '.items[]|select(.provisioner|test("\($nfs_csi)"))|(.parameters.server + ":" + .parameters.share)' + else + read -p "$(italics "?? Do you want to create a nfs storage class using \"nfs.csi.k8s.io\" driver?.........:$(faint "[y]") ")" yn + yn=${yn:-y} + case $yn in + [Yy]* ) + create_sc nfs.csi.k8s.io + ;; + [Nn]* ) + echo "$new_i PVC will be installed by the default \"ovirt-csi\" stotage class in RWO mode, some container may not start because of this." + ;; + esac + fi + else + while true; do + read -p "$(italics "?? Do you want to install the \"nfs.csi.k8s.io\" driver?(y/n)...............:$(faint "[y]") ")" yn + yn=${yn:-y} + case $yn in + [Yy]* ) + echo "Downloading and installing nfs.csi.k8s.io driver to cluster" + curl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/v4.7.0/deploy/install-driver.sh | bash -s v4.7.0 -- + read -p "$(italics "?? Do you want to create a nfs storage class using nfs.csi.k8s.io driver?.........:$(faint "[y]") ")" yn + yn=${yn:-y} + case $yn in + [Yy]* ) + create_sc nfs.csi.k8s.io + ;; + [Nn]* ) + echo "$new_i PVC will be installed by the default \"ovirt-csi\" stotage class in RWO mode, some container may not start because of this." + ;; + esac + break;; + [Nn]* ) + echo "$new_i PVC will be installed by the default \"ovirt-csi\" stotage class in RWO mode, some container may not start because of this." + ;; + esac + done + fi + while true; do + read -p "$(italics "?? Do you want to use \"nfs-csi\" storage class for \"$i\" volume? (y/n)....................................:$(faint "[y]") ")" yn + yn=${yn:-y} + case $yn in + [Yy]* ) + new_i=$(echo $i | sed 's/\./-/g' |sed 's/_/-/g') + echo "patching nfs-csi in $new_i-persistentvolumeclaim.yaml......................................." + cat $new_i-persistentvolumeclaim.yaml | yq -o json |jq '.spec.storageClassName="nfs-csi"|.spec.accessModes=["ReadWriteMany"]' | yq -P | sponge $new_i-persistentvolumeclaim.yaml + break;; + [Nn]* ) + echo "$new_i PVC will be installed by the default \"ovirt-csi\" stotage class in RWO mode, some container may not start because of this." + ;; + esac + done + fi + done + + +step "4" " Application deployment" +choice=$( +case $ENV in + local) echo -e "oc apply -f \"*.yaml\"\n";; + *) echo -e "cd $NAME-docker-$ENV && \'oc apply -f \"*.yaml\"\'\n";; +esac +) + +read -p "$(italics "?? Would you like to deploy $NAME on OKD $ENV?.......................................$(faint "[y]") ")" answer +answer=${answer:-y} +echo -e "(You can alternatively do it later by manually entering: $choice)" + +if [[ "$answer" == "y" ]]; + then + OKD=$(oc project 2>&1 >/dev/null) + echo $OKD + if [[ $(echo $OKD| grep "Unauthorized") != '' ]] + then + echo "First connect to your OKD cluster with \"export KUBECONFIG=path_to_kubeconfig\" and reexecute the script..........................." + exit 1 + else + release_pv $NAME + while true; do + read -p "$(italics "?? Would you like to create a new project? (y/n)....................................$(faint "[y]")"): " yn + yn=${yn:-y} + case $yn in + [Yy]* ) + read -p "$(italics "?? Enter the name of the project $(faint "[$namespace]")....................................:") " project + project=${project:-$namespace} + oc new-project $project + echo -e "Setting SCC anyuid to default SA.......................................\n" + oc adm policy add-scc-to-user anyuid -z default + read -p "$(italics "?? Do you want to authenticate against DockerHub? $(faint "[n]")"): " ynyn + ynyn=${ynyn:-n} + case $ynyn in + [Yy]* ) + read -p "$(italics "?? DockerHub server $(faint "[docker.io]")"): " dh_server + dh_server=${dh_server:-docker.io} + read -p "$(italics "?? DockerHub username" ): " dh_user + read -p "$(italics "?? DockerHub password" ): " dh_passwd + echo -e "Creation of docker secret for pulling images without restriction.......................................\n" + oc create secret docker-registry docker.io --docker-server=${dh_server} --docker-username=${dh_user} --docker-password=${dh_passwd} + oc secrets link default docker.io --for=pull + echo -e "\n" + ;; + esac + + break;; + [Nn]* ) + break;; + * ) echo "Please answer yes or no.";; + esac + done + + read -p "$(italics "?? Ready to deploy $name. Press \"Enter\" to begin.......................................")" answer + oc apply -f "*.yaml*" + echo -e "\n" + oc get pods + echo -e "\n" + step "5" "Copy of persistent volumes" + title "5.1" "Copy data to pvc of type bind" + copy_to_okd bind + title "5.2" "Copy data to pvc of type volume" + copy_to_okd volume + echo -e "\n" + fi + else + step "5" "Copy of persistent volumes" + title "5.1" "Copy data to pvc of type bind" + copy_to_okd bind + title "5.2" "Copy data to pvc of type volume" + copy_to_okd volume +fi + + +# Redémarrage des pods et URL de connexion + +step "FINAL" "Pods reload and connexion URL" +read -p "$(italics "?? Reloading pods to launch \"$NAME\" $(faint "[y]") : ?")" yn +yn=${yn:-y} +case $yn in + [Yy]* ) + echo "Restart all $NAME pods......................................." + oc rollout restart deploy + timeout 10 oc get pods -w;; + [Nn]* ) + echo "You should manually relaod pods before \"$NAME\" being up by typing \"oc rollout restart deploy\"";; +esac + +echo -e "\n Here is the list of configured services: \n" +oc get svc +read -p "$(italics "?? Enter a list of above services you want to expose: ")" services +for i in $services + do + oc expose svc $i + done + +URL=$(oc get route -o json | jq -r '[.items[]|.spec]|first|.host') +title "###" "Congratulations!" +echo -e "You can reach $NAME application at: " +blue "http://$URL\n" + + + + + diff --git "a/documentation/files/S\303\251lection_529.png" "b/documentation/files/S\303\251lection_529.png" new file mode 100644 index 0000000000000000000000000000000000000000..01eb7a4476e26e638d0750436a00f9b9fb296434 GIT binary patch literal 37645 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 literal 0 HcmV?d00001 diff --git a/functions.sh b/functions.sh new file mode 100644 index 0000000..28abdc0 --- /dev/null +++ b/functions.sh @@ -0,0 +1,715 @@ +#!/bin/bash + +### functions used by compose2manifests.sh + +help () { + echo -e "usage: ./compose2manifests.sh [ prod || test || dev || local ] [ appli_name ] [default || '' || secret || env_file | help] [kompose] [helm]\n" + echo -e "dev|test|prod: \tenvironnement sur lequel récupérer le .env. Local: fournir manuellement les '.env' et 'docker-compose.yml'" + echo -e "appli_name: \t\tnom de l'application à convertir" + echo -e "default or '' : \tGenerates cleaned appli.yml compose file to plain k8s manifests " + echo -e "env_file: \t\tGenerates cleaned advanced appli.yml with migrating plain 'environment' \n\t\t\tto 'env_file' statement, will generate k8s \"configmaps\" for common vars and \"secrets\" for vars containing 'PASSWORD' or 'KEY' as keyword" + echo -e "kompose: \t\tConverts appli.yml into plain k8s manifests ready to be deployed with \n\t\t\t'kubectl apply -f *.yaml" + echo -e "helm: \t\t\tKompose option that generates k8s manifest into helm skeleton for appli.yml\n" + echo -e "example: ./compose2manifests.sh local item env_file kompose\n" + echo -e "example: ./compose2manifests.sh prod qualimarc default kompose helm\n" + exit 1 +} + +blue () { + echo -e "${BLUE}$1${ENDCOLOR}" +} + +red () { + echo -e "${RED}$1${ENDCOLOR}" +} + +faint () { + echo -e "${FAINT}$1${ENDCOLOR}" +} + +italics () { + echo -e "${ITALICS}$1${ENDCOLOR}" +} + +bold () { + echo -e "${BOLD}$1${ENDCOLOR}" +} + +step () { + echo -e "\n\n"${YELLOW}################################################################################################################################${ENDCOLOR}" +${YELLOW}STEP $1: $2${ENDCOLOR}" +} + +title () { + echo -e "\n${GREEN}$1> ##################################################${ENDCOLOR} +${GREEN}############ $2 ${ENDCOLOR} +${GREEN}#######################################################${ENDCOLOR}" +} + +message () { + if [ $(echo $?) = "0" ]; + then + echo -e "${BLUE}...OK${ENDCOLOR}"; + else echo -e "${RED}echec!!!${ENDCOLOR}"; + exit 1; + fi +} + +install_bin () { + if ! [ -f /usr/local/bin/$1 ] && ! [ -f /usr/bin/$1 ];then + case $1 in + jq) + BIN="jqlang/jq/releases/latest/download/jq-linux-amd64";; + yq) + BIN="mikefarah/yq/releases/latest/download/yq_linux_amd64";; + docker-compose) + BIN="docker/compose/releases/latest/download/docker-compose-linux-x86_64";; + kompose) + BIN="kubernetes/kompose/releases/download/v1.28.0/kompose-linux-amd64";; + oc) + wget -q okd-project/okd/releases/download/4.G1263.0-0.okd-2023-02-18-033438/openshift-client-linux-4.12.0-0.okd-2023-02-18-033438.tar.gz \ + -O /usr/local/bin/ | \ + tar xzf - + chmod +x {kubectl,oc};; + jc) + wget -q https://github.com/kellyjonbrazil/jc/releases/download/v1.25.3/jc-1.25.3-linux-x86_64.tar.gz \ + -O /usr/local/bin/ | \ + tar xzf - + chmod +x jc;; + *) + ;; + esac + echo "Installing $(blue $1)......................................." + sudo wget -q https://github.com/${BIN} -O /usr/local/bin/$1 && sudo chmod +x /usr/local/bin/$1 + fi + if ! [ -f /usr/bin/sponge ];then + echo "Installing sponge......................................." + case $(cat /etc/os-release | grep ID_LIKE) in \ + *debian*) \ + apt install moreutils -y;; \ + *rhel*) \ + if [[ "$(cat /etc/os-release | grep VERSION_ID)" =~ .*8.* ]];then + dnf config-manager --set-enabled powertools powertools + else + dnf config-manager --set-enabled powertools crb + fi + dnf -q install moreutils -y;; \ + *) \ + echo "Not supported plateform!" \ + exit 1;; \ + esac + fi + case $1 in + jq|yq) $1 --version;; + kompose) echo "kompose $($1 version)";; + jc) $1 -v |head -1;; + *) $1 version;; + esac +} + +set_ssh_key() { + + if [[ $( ls ~/.ssh | grep "id" ) == '' ]] + then + read -p "$(italics "?? No pub keys have been found. Do you want to generate? $(faint "[y]")"): " yn3 + yn3=${yn3:-y} + case $yn3 in + [Yy]* ) + ssh-keygen;; + [Nn]* ) + italics "You must first install some pub key before using this script" + exit;; + esac + else + if [ -z "$key" ]; then + echo -e "Here are the available public keys in your home directory:" + for k in $(ls ~/.ssh/ |grep pub|cut -d"." -f1); do blue $k; done + read -p "$(italics "?? Which one do you want to use to connect to your Docker hosts? $(faint "[id_rsa]")"): " key + key=${key:-id_rsa} + blue $key + fi + fi +} + +testing_ssh() { + +echo "Checking ssh connectivity to Docker hosts to bind ...." +for i in $docker_hosts + do + SSH=$(ssh -q -o "BatchMode=yes" -o "ConnectTimeout=3" root@${i}.${domain} "echo 2>&1" && echo "OK" ) + if [[ $(echo $SSH) == "OK" ]] + then + echo "Connexion to root@${i}.${domain} ........... $(blue OK)" + else + echo "Connexion to root@${i}.${domain} ........... $(red NOK)" + read -p "$(italics "?? Do you want to install $key to root@${i}.${domain}: $(faint "[y]")?") " yn + yn=${yn:-y} + while true; do + case $yn in + [Yy]* ) + set_ssh_key + echo -e "Installing pub keys......." + ssh-copy-id root@${i}.${domain} > /dev/null + message + break;; + [Nn]* ) + italics "You must first install some pub key before using this script" + exit + break;; + esac; done + fi + done + +} + +ask_testing_ssh() { +echo -e "${YELLOW}!!! Warning !!!${ENDCOLOR}" +read -p "$(italics "?? Do you want to check ssh connectivity? If a host is not reacheable, pub key will be installed.[no]: ")" yn +yn=${yn:-n} +while true; do + case $yn in + [Yy]* ) + testing_ssh + break;; + [Nn]* ) + echo "Assuming Docker hosts are available without any password..." + break;; + esac +done +} + +fetch() { + if [[ -f "$1" ]]; + then + echo "$(blue \"$1\") ready to be used" + else + if [[ $1 == "docker-compose.yml" ]] + then + italics "\"$1\" has not been found. Please check https://raw.githubusercontent.com/abes-esr/$NAME-docker/develop/docker-compose.yml and retry" + elif [[ $1 == ".env" ]] + then + echo "$(blue \"$1\") has not been found. Please check $(blue $docker_host:/opt/pod/$NAME-docker/.env) and retry" + fi + exit + fi +} + +get_running_docker() { + echo "" + read -p "$(italics "?? If you know the Docker host where \"$NAME\" is currently running on, please enter the hostname (not fqdn), else type \"enter\" to automatically find it: ")" hostname + # hostname=${hostname:-diplotaxis2-test} + if [ -z $hostname ] + then + docker_hosts= + read -p "$(italics "?? Please enter the list of your Docker hosts hostnames: ")" docker_hosts + docker_hosts=${docker_hosts:-"diplotaxis1 diplotaxis2 diplotaxis3 diplotaxis4 diplotaxis5 diplotaxis6 diplotaxis7"} + set -- $(echo $docker_hosts) + if [[ -n $ENV ]] && [[ "$ENV" != "local" ]]; + then + set -- "${@/%/-$ENV}" + fi + docker_hosts=$(echo $@) + blue "$docker_hosts" + title "1.4" "SSH connexion validation" + ask_testing_ssh + echo "Searching which Docker host \"$NAME\" is currently running on ......" + + NAME_SHORT=$(echo $NAME | cut -d"-" -f1) + + diplo=$( \ + for i in $docker_hosts + do + ssh root@${i}.${domain} docker ps --format json | jq --arg i "${i}" '{"docker_host": ($i), nom: .Names}'; \ + done \ + | jq -rs --arg docker_hosts "$i" --arg var "$NAME_SHORT" '[.[] | select(.nom | test("^\($var)-.*"))]|first|."docker_host"' + ); \ + else + diplo="$hostname" + fi + + blue "\"$NAME\" is running on $diplo\n" + mkdir $NAME-docker-${ENV} && cd $NAME-docker-${ENV} + + docker_host="${diplo}.${domain}" +} + +# Patch ReadOnlyMany pvc to ReadWriteOnly. The readOnly feature will be later executed with the "readOnly:"" true directive into deployment +patch_RWO () { +for i in $(grep ReadOnlyMany *persistent* |cut -d: -f1); + do + echo "Patching \n ReadOnlyMany modeAccess to ReadWriteOnce in $i......................................." + sed -i 's/ReadOnlyMany/ReadWriteOnce/g' $i; + done +} + +patch_expose_auto () { + for service in $services; + do + port=$(ssh root@$docker_host docker inspect $service | jq -cr '[.[].NetworkSettings.Ports|to_entries[]|.key|split("/")|.[0]']) + port=${port:-[]} + if [[ $port != '[]' ]] + then + blue "Patching ports $port for service $service ............" + cat $CLEANED | yq -o json| jq | jq --arg service "$service" --argjson port "$port" '.services."\($service)".expose+=$port' \ + |sponge $CLEANED + fi + done + cat $CLEANED | yq -P | sponge $CLEANED +} + +patch_expose () { + echo "You may define them one by one so as the conversion to be successfull" + for service in $services; + do + read -p "$(italics "?? $service: Enter port number to expose the service $(faint "(press to leave empty)"): ")" port + if [[ -n $service ]] + then + if [[ -n $port ]] + then + cat $CLEANED | yq -o json | jq --arg service "$service" --arg port "$port" '.services."\($service)".expose+=[ "\($port)" ]' \ + |sponge $CLEANED + fi + fi + done + cat $CLEANED | yq -P | sponge $CLEANED +} + +# Patch *.txt file to remove '\n' character based in 64 +patch_secret () { + for i in $(ls *secret*yaml); \ + do \ + echo "Patching \n character in $i......................................."; + cat $i | yq eval -ojson \ + | jq -r '.data|=with_entries(.value |=(@base64d|sub("\n";"")|@base64))' \ + | yq eval - -P \ + | sponge $i; \ + done +} + +# Patch secretKeys +patch_secretKeys () { + for i in $(ls *deployment*); + do + echo "patching lowercase in $i......................................." + cat $i| yq eval -ojson | + jq '.spec.template.spec.containers + |= map( + (.env + |= map( + if (.name|test("SECRET|PASS|KEY")) + then .valueFrom + |= with_entries(.key="secretKeyRef" + |.value.name=(.value.key|ascii_downcase|gsub("_";"-")) + |.value.key|=(ascii_downcase|gsub("_";"-")) + ) + else . + end + ) + )? // . + )' | + yq eval -P | sponge $i; + done +} + +# Patch networkpolicy to allow ingress + patch_networkPolicy () { + echo "patching ingress in $NAME-docker-$ENV-default-networkpolicy.yaml......................................." + NETWORK=$(ls | grep networkpolicy) + cat $NETWORK | + yq eval -ojson | + jq '.spec.ingress|= + map(.from |= .+ [{"namespaceSelector":{"matchLabels":{ "policy-group.network.openshift.io/ingress": ""}}}])'| + yq eval -P | sponge $NETWORK +} + +create_pv2() { +nfs_mount_points=$(ssh root@$docker_host mount | \ + jc --mount | \ + jq -r '.[]|select(.type|test("nfs")) + |{ + path: .filesystem|split(":")|last, + rep: .filesystem|split("/")|last|gsub("_";"-")|gsub("\\.";"-"), + mount_point: .mount_point, + server: .filesystem|split(":")|first + }' \ + ) + +for i in $(echo $nfs_mount_points|jq -r '."mount_point"|split("/")|last') + do + nfs_service=$(cat $2.yml | \ + yq -ojson | \ + jq -r --arg i "$i" '.services|to_entries[]|select(.value|has("volumes"))|select(.value.volumes[]|select(.source|test("\($i)")))?' ) + if [[ -n $nfs_service ]] + then + nfs_services=$nfs_service + fi + done + +for i in $(echo $nfs_services|jq -r '.key'); do \ +vol_nb=$(cat $NAME.yml|yq -ojson |jq --arg i "$i" --arg pwd "${PWD##*/}" -r '.services|to_entries[] + |select(.key=="\($i)") + |.value.volumes|length') + +for ((index=0; index<$vol_nb; index++ )); do \ +source=$(echo $nfs_services \ + |jq --arg i "$i" --arg pwd "${PWD##*/}" --arg index "$index" -r \ + 'select(.key==$i)|( + if (.value.volumes[$index|tonumber].source|split("\($pwd)/")|.[1] != null) + then .value.volumes[$index|tonumber].source|split("\($pwd)/")|.[1] + else .value.volumes[$index|tonumber].source|split("/")|.[1] + end + )' \ + ) + +NFS_PATH=$(echo $nfs_mount_points |jq -r --arg source "$source" 'select("\(.mount_point)"|test("\($source)$")).path') +NFS_SERVER=$(echo $nfs_mount_points |jq -r --arg source "$source" 'select("\(.mount_point)"|test("\($source)$")).server') + +subpath=$(echo $nfs_services \ + |jq --arg i "$i" --arg pwd "${PWD##*/}" --arg source "$source" --arg index "$index" -r \ + 'select(.key==$i)|( + if (.value.volumes[$index|tonumber].source|split("\($pwd)/")|.[1] != null) + then .value.volumes[$index|tonumber].source|split("\($pwd)/")|.[1]|split("\($source)/")|last + else .value.volumes[$index|tonumber].source|split("\($source)/")|last + end + )' \ + ) + +if [ -n "$NFS_PATH" ];then +source_renamed=$(echo $source | sed 's/_/-/g' | sed 's/\./-/g' | sed 's/\//-/g' | tr '[:upper:]' '[:lower:]') +echo "Creating $i-pv$index-nfs-$source_renamed-persistentvolume.yaml ........................................." +cat < $i-pv$index-nfs-$source_renamed-persistentvolume.yaml +apiVersion: v1 +kind: PersistentVolume +metadata: + name: $i-pv${index}-nfs-$namespace-$ENV +spec: + capacity: + storage: 8Ti + accessModes: + - ReadWriteMany + nfs: + path: $NFS_PATH + server: $NFS_SERVER + persistentVolumeReclaimPolicy: Retain +EOF +create_pvc_nfs $ENV +patch_deploy_nfs $ENV +fi +done +done + +} + +create_pvc_nfs() { + + echo "patching $source_renamed in $i-claim$index-nfs-persistentvolumeclaim.yaml ......................................." + cat $i-claim$index-persistentvolumeclaim.yaml | + yq eval -ojson | + jq --arg project "$project" --arg name "$i" --arg env "$ENV" --arg namespace "$namespace" --arg index "$index" \ + '.metadata.name|="\($name)-claim\($index)-nfs-\($namespace)-\($env)" + |.metadata.labels."io.kompose.service"|="\($name)-claim\($index)-nfs-\($namespace)-\($env)" + |.spec.resources.requests.storage="8Ti" + |.spec.volumeName="\($name)-pv\($index)-nfs-\($namespace)-\($env)" + |.spec.storageClassName=""|.spec.accessModes=["ReadWriteMany"]' | + yq eval -P | sponge $i-claim$index-nfs-persistentvolumeclaim.yaml + echo "deleting $i-claim$index-persistentvolumeclaim.yaml" + rm -f $i-claim$index-persistentvolumeclaim.yaml +} + +patch_deploy_nfs() { + + oldname=$i-claim${index} + newname="$i-claim${index}-nfs-${namespace}-$ENV" + cat $i-deployment.yaml | yq -ojson | \ + jq --arg newname "$newname" --arg oldname "$oldname" --arg subpath "$subpath" \ + '(.spec.template.spec.containers[0].volumeMounts[]|select(.name=="\($oldname)"))+= ({subPath:"\($subpath)"}|.name|="\($newname)")| + (.spec.template.spec.volumes[]|select(.name=="\($oldname)"))|=(.name|="\($newname)"|.persistentVolumeClaim.claimName|="\($newname)")' | yq -P | sponge $i-deployment.yaml +} + +patch_configmaps() { + + for i in $(ls | grep "deployment") + do + echo "patching configMaps in $i ......................................." + claims=$(cat $i | yq -ojson| jq -r '.spec.template.spec.containers[].volumeMounts[]?|select((.mountPath|test("(\\.[^.]+)$")) and (.name|test("claim"))).name') + + for j in $claims + do + echo "deleting ${j}-persistentvolumeclaim.yaml ......................................." + rm -f ${j}-persistentvolumeclaim.yaml + cat $i | yq -ojson| \ + jq -r --arg j $j 'del(.spec.template.spec.volumes[]?|(select(.name=="\($j)")))'| \ + sponge $i + done + + services=$(cat $i | yq -ojson| jq -r '.spec.template.spec.containers[].volumeMounts[]?|select((.mountPath|test("(\\.[^.]+)$")) and (.name|test("claim")))|(.name|split("-claim")|.[0]) + "-" + (.mountPath|split("/")|last|gsub("_";"-")|gsub("\\.";"-")|ascii_downcase)') + + for j in $services + do cat $i | yq -ojson| \ + jq -r --arg j $j '((.spec.template.spec.containers[].volumeMounts[]?|select((.mountPath|test("(\\.[^.]+)$")) and (.name|test("claim")) )) |= {mountPath: .mountPath, name: ((.name|split("-claim")|.[0]) + "-" + (.mountPath|split("/")|last|gsub("_";"-")|gsub("\\.";"-")|ascii_downcase)), subPath: (.mountPath|split("/")|last)})|.spec.template.spec.volumes+=[{configMap: {defaultMode: 420, name: $j}, name: $j}]' | \ + sponge $i + done + done +} + +create_configmaps() { + CM=$(cat $NAME.yml | yq -o json | jq -r --arg pwd "$NAME-docker-$ENV" '.services[].volumes|select(.!=null)|.[]|select(.type == "bind")|select(.source|test("(\\.[^.]+)$"))|select(.source|test("sock")|not).source|split($pwd)|.[1]?') + CM_RENAMED=$(cat $NAME.yml | yq -o json | jq -r --arg pwd "$NAME-docker-$ENV" '.services|to_entries[]|{name: .key, volumes: (.value|.volumes|select(.!=null)|.[]|select(.type == "bind")|select(.target|test("(\\.[^.]+)$"))|select(.target|test("sock")|not).target|split("/")|last)}|(.name + "-" + (.volumes|split("/")|last|gsub("\\/";"-")|gsub("\\.";"-")|gsub("\\_";"-")|ascii_downcase))') + CM_RENAMED_short=$(cat $NAME.yml | yq -o json | jq -r --arg pwd "$NAME-docker-$ENV" '.services|to_entries[]|{name: .key, volumes: (.value|.volumes|select(.!=null)|.[]|select(.type == "bind")|select(.target|test("(\\.[^.]+)$"))|select(.target|test("sock")|not).target|split("/")|last)}|((.volumes|split("/")|last))') + + + declare -a tab_CM + declare -a tab_CM_RENAMED + declare -a tab_CM_RENAMED_short + + index=-1 + for i in $CM + do + index=$(($index + 1)) + tab_CM[$index]=$i + done + + index=-1 + for i in $CM_RENAMED + do + index=$(($index + 1)) + tab_CM_RENAMED[$index]=$i + done + + index=-1 + for i in $CM_RENAMED_short + do + index=$(($index + 1)) + tab_CM_RENAMED_short[$index]=$i + done + + if [[ ! -d './volumes' ]]; + then + mkdir volumes + fi + if [[ $index != -1 ]] + then + echo "Rep: $(pwd)" + echo "Mounting root@$docker_host:/opt/pod/$NAME-docker/ ./volumes/" + sshfs root@$docker_host:/opt/pod/$NAME-docker/ ./volumes/ + message + + for ((i=0; i<=$index; i++ )) + do + echo "creating configMap file ${tab_CM_RENAMED[$i]}-configmap.yaml ......................................." + oc create cm ${tab_CM_RENAMED[$i]} --from-file=./volumes/${tab_CM[$i]} --dry-run=client -o json | jq -r --arg z "${tab_CM_RENAMED_short[$i]}" '(if has("data") then .data|=with_entries(.key="\($z)") else .binaryData|=with_entries(.key="\($z)") end)' | yq -P > ${tab_CM_RENAMED[$i]}-configmap.yaml + done + fusermount -u $PWD/volumes + fi +} + +patch_labels() { + for i in $(ls | egrep "*env-configmap.yaml") + do + echo "patching labels of $i .............. " + cat $i |yq -ojson |jq -r '(.metadata|.labels."io.kompose.service")=.metadata.name' | yq -P | sponge $i + done +} + +create_sc() { + read -p "$(italics "?? Enter NFS server name $(faint "[methana.v102.abes.fr]")...........: ")" server_name + server_name=${server_name:-methana.v102.abes.fr} + read -p "$(italics "?? Enter NFS share $(faint "[/pool_SAS_2/OKD]")............: ")" nfs_share + nfs_share=${nfs_share:-/pool_SAS_2/OKD} + cat < /dev/null + if [ "$?" == "0" ] + then + mount_point=$(echo $nfs_mount_points |jq -rs --arg j "$j" '.[]|select(.mount_point|test("\($j)")).mount_point|split("/")|last') + fi + done + } + + # Change size of volumeclaim yaml declaration + for i in "${volumes[@]}"; + do + select_nfs_mount_point + size=$(echo $i | jq -r '.value.volumes[]?.size'|uniq) + source=$(echo $i | jq -r '.value.volumes[]?.source'|uniq) + service=$(echo $i | jq -r '.key'|uniq) + if [ "$mount_point" != "${source##*/}" ] ; then + for j in $(echo $i | jq -r --arg service "$service" 'select(.key=="\($service)").value.volumes[].target') + do + index=$(echo "${volumes[*]}" |jq -s --arg j "$j" --arg service "$service" '[group_by(.key)[]|.[]|select(.key=="\($service)").value.volumes[0]]|[.[].target]|index("\($j)")') + if [[ $1 = "bind" ]] + then + file="${service}-claim${index}-persistentvolumeclaim.yaml" + file_name="${service}-claim${index}" + else + file="${source}-persistentvolumeclaim.yaml" + file_name="${source}" + fi + + status="" + search_pvc=$(oc get pvc -o json | jq --arg file_name $file_name '.items[]|.metadata|select(.name=="\($file_name)")') + if [ -n "$search_pvc" ] + then + while [ "$status" != "Bound" ] && [ -n "$file_name" ] + do + echo "Waiting for pvc to be ready...." + status=$(oc get pvc $file_name -o json | jq -r '.status.phase') + sleep 5 + done + + is_nfs=$(oc get pvc $file_name -o json | jq -r '.spec.storageClassName|test("nfs")') + if [[ $is_nfs != "true" ]] + then + echo "Resizing $file_name to $size ................" + cat ${file} | + yq eval -ojson| + jq --arg size "$size" '.spec.resources.requests.storage=$size'| + yq eval -P | + sponge ${file} + oc apply -f ${file} + echo "" + fi + fi + done + fi + done + + read -p "$(italics "?? Would you like to copy current data to okd volume of type $1 (may be long)? (y/n).......................................$(faint "[y]")")" answer + answer=${answer:-y} + private_key=$(cat ~/.ssh/${key:-id_rsa}) + if [[ "$answer" = "y" ]]; + then + for i in "${volumes[@]}"; + do + select_nfs_mount_point + service=$(echo $i | jq -r '.key') + target=$(echo $i | jq -r '.value.volumes|last.target') + source=$(echo $i | jq -r '.value.volumes|last.source') + + if [ "$mount_point" != "${source##*/}" ]; then + if [[ "$(echo $source| grep backup)" != '' ]]; + then + src=$source + else + if [[ $1 = "bind" ]] + then + src="/opt/pod/${NAME}-docker/${source}" + else + src="/var/lib/docker/volumes/${NAME}-docker_${source}/_data/" + fi + fi + size=$(echo $i | jq -r '.value.volumes[].size') + echo "###########################################################################" + echo -e "$service:\nPaste those commands to copy data:" + echo -e "${YELLOW}from${ENDCOLOR} ${docker_host}:${src}/ ${YELLOW}to${ENDCOLOR} persistent volume ($size):\n" + blue "mkdir /root/.ssh && echo \"$private_key\" > /root/.ssh/id_rsa && chmod 600 -R /root/.ssh; \ +if [ \"\$(cat /etc/os-release|grep "alpine")\" = '' ]; \ +then apt update && apt install rsync openssh-client -y; \ +else apk update && apk -f add rsync openssh-client-default openssh-client; fi; \ +rsync -av -e 'ssh -o StrictHostKeyChecking=no' ${docker_host}:${src}/ ${target}/; \ +exit" + echo "###########################################################################" + POD=$(oc get pods -o json| jq -r --arg service "$service" '.items[]|.metadata|select(.name|test("\($service)-[b-df24-9]+-[b-df-hj-np-tv-z24-9]{5}"))|.name') + oc debug $POD --as-root=true + fi + done + fi + else + blue "No volume of type \"$1\"...... going on" +fi +} + +release_pv() { + for i in $(oc get pv -ojson | jq -r --arg NAME "$1" '.items[].metadata|select( (.name|test("\($NAME)")) and (.name|test("nfs")) ).name') + do + echo -e "Releasing pv applis-$i..........................................................\n" + oc patch pv $i -p '{"spec":{"claimRef": null}}' + done +} \ No newline at end of file