Gestion de serveur distribué avec GitHub
Exemple de code Python pour mettre à jour des fichiers à partir d'un référentiel GitHub
J'ai utilisé ce script pour mettre à jour un ensemble de scripts de gestion qui s'exécutaient sur de nombreux serveurs. J'aurais pu simplement vérifier les scripts avec un "git pull", mais je voulais des contrôles supplémentaires sur quel script était installé sur quel serveur, j'ai donc écrit un script Python pour le faire.
J'appelle cela une approche distribuée ou décentralisée, car contrairement à une autorité centrale telle qu'un serveur maître Salt, Chef ou Puppet qui envoie de nouveaux scripts de gestion à tous les serveurs, cela permet aux serveurs d'extraire des scripts mis à jour de GitHub.
Le script utilise la bibliothèque PyGitHub , donc commençons par l'installer, ainsi que la bibliothèque Yaml.
pip3 install PyGithub
pip3 install pyaml
Obtenir un jeton d'API auprès de GitHub
Pour utiliser le script suivant, vous devez d'abord obtenir un jeton d'API auprès de GitHub. Dans votre compte GitHub, accédez à "Paramètres" et "Paramètres du développeur", puis cliquez sur "jeton d'accès personnel" et créez un nouveau jeton. Aucune autorisation supplémentaire n'est nécessaire pour le moment.
Vous devez absolument protéger ce jeton d'accès personnel car il permet d'accéder à votre compte, pas à n'importe quel référentiel particulier. Pour cela, j'ai décidé d'utiliser la bibliothèque RSA-crypto pour le chiffrer. Le code suivant installera la bibliothèque, créera un nouveau jeu de clés RSA et chiffrera le jeton d'accès personnel dans un fichier de configuration en tant que valeur de l'option nommée ghe_token . Voir Public/Private Key RSA Encryption et Python Encryption pour plus de détails sur l'utilisation de cette bibliothèque et de l'outil de ligne de commande associé.
pip3 install rsa-crypto
rsa_crypto create
Enter key password:
Re-enter key password:
Creating key...
Created password-protected private/public keys file /Users/me/rsa_key.bin
Use the "extract" keyword to create public and private key files.
touch ~/.rsa_values.conf
rsa_crypto set -o ghe_token
Using key: /Users/me/rsa_key.bin
Opening encrypted key /Users/me/rsa_key.bin
Enter key password:
Enter value:
set section: option:ghe_token
Updated /Users/me/.rsa_values.conf
Le script Python sera alors configuré pour lire et déchiffrer l'"option" de configuration ghe_token afin de récupérer et d'utiliser le jeton d'accès personnel. Si cela semble trop complexe, n'hésitez pas à trouver une solution différente, mais s'il vous plaît, n'intégrez jamais de jeton d'accès directement dans votre script, il y a de fortes chances qu'il se retrouve sur GitHub pour que quiconque puisse le voir et l'utiliser !
Le script Python
Avant de pouvoir utiliser ce script, vous devrez modifier quelques paramètres, modifier ghe_repo pour pointer vers votre propre référentiel et local_base_path pour pointer vers le répertoire local de votre système où vous souhaitez que les scripts de gestion de serveur résident.
Le script suppose que vous utilisez le GitHub public, mais le code nécessaire si vous utilisez un référentiel privé GitHub Enterprise interne à votre organisation a également été inclus et commenté.
Vous pouvez trouver ce script dans un référentiel GitHub .
Le script va d'abord récupérer un fichier de configuration update_scripts.yaml. Ce fichier contient trois sections :
- update_always - contient une liste de fichiers qui doivent toujours être extraits de GitHub
- update_if_present - contient une liste de fichiers qui doivent être mis à jour à partir de GitHub uniquement s'ils sont présents
- remove - contient une liste de fichiers qui doivent être supprimés du serveur local
Ce fichier vous permet de contrôler de manière centralisée les scripts que vous souhaitez déployer sur tous vos serveurs. Si vous avez besoin d'un nouveau script installé, poussez-le sur GitHub et ajoutez une entrée dans le fichier de configuration, et vos serveurs téléchargeront les nouveaux scripts lorsque ce script sera exécuté. Vous pouvez également "inciter" vos systèmes à télécharger des scripts à partir de la section update_if_present simplement en créant le fichier à l'aide de la commande touch , puis en exécutant ce script de mise à jour. Le script se mettra également à jour s'il est inclus dans la liste.
Les trois sections des fichiers seront traitées, les fichiers des sections update_always et update_if_present ne seront mis à jour que dans le hachage GitHub différent du hachage local, ce qui rend l'extraction plus efficace. Le script rendra également tous les scripts python exécutables pour l'utilisateur actuel.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
__author__ = "Videre Research, LLC"
__version__ = "1.0.2"
'''
Install and configure the server management scripts and other supporting scripts.
'''
# I M P O R T S ###############################################################
import os
import sys
import logging
import traceback
import base64
import github
import yaml
import hashlib
import rsa_crypto
import argparse
if (sys.version_info > (3, 0)):
from urllib.parse import urljoin
else:
from urlparse import urljoin
# G L O B A L S ###############################################################
# # Uncomment if you are using your own internal GitHub Repository
# ghe_organization = 'my_ghe_repo'
# ghe_hostname = mydomain.com
ghe_repo = 'Christophe-Gauge/GitHub'
remote_base_path = '/'
local_base_path = '/opt/scripts'
remote_config_file = 'update_scripts.yaml'
ghe_branch = 'main'
logger = logging.getLogger()
logging.basicConfig(level=logging.INFO)
logger.info("Path: %s" % (os.path.realpath(__file__)))
logger.info("Version: %s" % (__version__))
args = argparse.Namespace(option='ghe_token')
ghe_token = rsa_crypto.decrypt_value(args)
# C O D E #####################################################################
def get_ghe(remote_file, repo, always_update):
"""Get a file from GHE and save it locally"""
my_file_name = os.path.basename(remote_file)
remote_file_name = urljoin(remote_base_path, remote_file)
local_file_name = os.path.join(local_base_path, my_file_name)
logger.info("Retrieving remote GHE file %s%s to %s" % (repo.full_name, remote_file_name, local_file_name))
try:
remoteSHA = repo.get_contents(remote_file_name, ref=ghe_branch).sha
except github.UnknownObjectException as e:
logger.error(f"Remote file not found {remote_file_name}")
return
except Exception as e:
logger.error("Error {0}".format(str(e)))
logger.error(traceback.format_exc())
return
# If the file exists then let's get the hash to see if an update is needed
if os.path.exists(local_file_name):
# Compute the SHA1 hash of the local file
with open(local_file_name, 'rb') as file_for_hash:
data = file_for_hash.read()
filesize = len(data)
content = "blob " + str(filesize) + "\0" + data.decode('utf-8')
encoded_content = content.encode('utf-8')
localSHA = hashlib.sha1(encoded_content).hexdigest()
if remoteSHA == localSHA:
logger.info('File is present, hash is the same, we already have the latest file, NOT updating.')
return
else:
logger.info('File is present, hash is different %s - %s' % (remoteSHA, localSHA))
else:
# This flag indicates that a file should only be updated if it already exists
if not always_update:
logger.info('File is not present NOT updating')
return
try:
file_contents = repo.get_contents(remote_file_name, ref=ghe_branch)
local_file_content = str(base64.b64decode(file_contents.content).decode('utf-8', 'ignore'))
# Write the new file to disk
with open(local_file_name, "w") as text_file:
text_file.write(local_file_content)
if my_file_name.endswith('.py'):
os.chmod(local_file_name, 0o700)
else:
os.chmod(local_file_name, 0o400)
logger.info('File was updated')
except Exception as e:
logger.error("Error {0}".format(str(e)))
logger.error(traceback.format_exc())
def main():
"""Main function."""
gh = github.Github(login_or_token=ghe_token)
repo = gh.get_repo(ghe_repo)
# # Uncomment if you are using your own internal GitHub Repository
# gh = github.Github(base_url=f"https://{ghe_hostname}/api/v3", login_or_token=ghe_token)
# org = gh.get_organization(ghe_organization)
# repo = org.get_repo(ghe_repo)
if not os.path.exists(local_base_path):
os.makedirs(local_base_path)
remote_file_name = urljoin(remote_base_path, remote_config_file)
logger.info("Retrieving remote GHE file %s%s" % (repo.full_name, remote_file_name))
try:
file_contents = repo.get_contents(remote_file_name, ref=ghe_branch)
text_contents = str(base64.b64decode(file_contents.content).decode('utf-8', 'ignore'))
file_list = yaml.load(text_contents, Loader=yaml.SafeLoader)
logger.info(yaml.safe_dump(file_list, default_flow_style=False))
except Exception as e:
if e.args[0] == 404:
logger.error(f"Remote file not found {remote_file_name}")
sys.exit(1)
else:
logger.error("Error {0}".format(str(e)))
logger.error(traceback.format_exc())
sys.exit(1)
for file in file_list['update_always']:
logger.info(file)
get_ghe(file, repo, True)
for file in file_list['update_if_present']:
logger.info(file)
get_ghe(file, repo, False)
for file in file_list['remove']:
if os.path.exists(file):
os.remove(file)
logger.info('File %s was deleted' % file)
sys.exit(0)
###############################################################################
if __name__ == "__main__":
main()
# E N D O F F I L E #######################################################
Tagged with:
GitHub