構成ファイルを GitHub にバックアップする

重要なサーバー構成ファイルを GitHub リポジトリーにコミットする Python コードのサンプル

C05348A3-9AB8-42C9-A6E0-81DB3AC59FEB
           

この演習は、GitHub を使用した分散サーバー管理で説明されている演習の逆です。今回は、HAproxy や nginx ファイルなどの重要なサーバー構成ファイルを GitHub にバックアップします。これらのファイルの編集は通常、試行錯誤によって行われます。ファイルを編集し、構文を確認し、プロセスを再起動して、変更が意図した動作をしていることを確認します。これは明らかに、本番環境ではなく、開発システムで行われます。

これらのファイルを GitHub に保存することは絶対に良い考えですが、変更をリポジトリにコミットしてサーバーからプルする通常の GitHub ワークフローは実際には適用されません。これらの構成ファイルは通常、環境固有のものでもあります。DEV 構成ファイルを運用サーバーに持ち込むことはできません。DNS 名、さまざまなログ パラメーター、およびそれらを異なるものにする多くの要素があります。個人的には、使用するのに最適なワークフローは、通常の GitHub ワークフローのほぼ逆であり、ファイルに変更を加え、テストして意図したとおりに機能することを確認し、ファイルの新しいバージョンを GitHub にコミットすることであることがわかりました。バックアップ。バックアップは、サーバーを再構築する必要がある場合に使用することも、問題が発生した場合に次の変更を以前の作業バージョンに戻すためのバックアウト オプションとして使用することもできます。

これらのファイルが変更されたときに GitHub にコミットすることが、この Python スクリプトの基本的な目的です。このスクリプトは、パスワードなどの特定のパラメーターを秘匿化することもできますが、これはもちろん、GitHub やその他のコード リポジトリには絶対に保存したくないものです。このスクリプトのもう 1 つの利点は、サーバーごとに個別のリポジトリを必要とするのではなく、サーバー名にちなんで名付けられたディレクトリ内の単一の GitHub リポジトリにファイルをアップロードできることです。

スクリプトはPyGitHub ライブラリを使用するので、Yaml ライブラリと同様にインストールすることから始めましょう。

pip3 install PyGithub

GitHub から API トークンを取得する

次のスクリプトを使用するには、まず GitHub から API トークンを取得する必要があります。 GitHub アカウントで、[設定]、[開発者設定] に移動し、[個人用アクセス トークン] をクリックして新しいトークンを作成します。この記事に従って、スコープとして「レポ」を選択します。

特定のリポジトリだけでなく、アカウントへのアクセスを許可するため、その個人用アクセス トークンを確実に保護する必要があります。このため、 RSA-crypto ライブラリを使用して暗号化することにしました。次のコードは、ライブラリをインストールし、新しい RSA キー セットを作成し、構成ファイル内の個人アクセス トークンをghe_tokenオプションの値として暗号化します。そのライブラリと関連するコマンドライン ツールの使用に関する詳細については、公開/秘密キー RSA 暗号化Python 暗号化を参照してください。

最初に新しい暗号化を作成しますが、まだ作成していないと仮定して、上記で取得した GitHub API トークンの値を構成ファイルのghe_tokenという名前のエントリ (オプション) として設定します。

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.
 
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

Python スクリプトは、 ghe_token構成の「オプション」を読み取って復号化し、個人用アクセス トークンを取得して使用するように構成されます。これが複雑すぎると思われる場合は、自由に別の解決策を見つけてください。ただし、アクセス トークンをスクリプトに直接埋め込まないでください。最終的に GitHub にアップロードされ、誰でも閲覧および使用できるようになる可能性が高くなります。

Python スクリプト

ソースコードは GitHub でも入手できます。

構成変数は非常に簡単です。 file_listには、ローカルに存在する場合に GitHub にコミットするファイルのリストが含まれています。

ghe_repo変数は、GitHub リポジトリを指す必要があります。構成ファイルをバックアップする目的で別のリポジトリを使用することを強くお勧めします。分散サーバー管理と GitHubスクリプトを使用する場合は、別のリポジトリを使用することを強くお勧めします。

ghe_branch変数を使用すると、GitHub の使用方法に応じて、別のブランチを指すことができます。

remote_base_path変数は、リモート パスを / で開始する必要があった PyGitHub ライブラリの以前のバグのためにのみ存在しますが、これはもはや当てはまりません。

スクリプトはログ ファイルを作成し、スクリプトを実行しているユーザーの名前を決定して、コミット コメントとして含めようとします。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
                        print_function, unicode_literals)
 
__author__ = "Christophe Gauge"
__version__ = "1.0.4"
 
'''
Backup HAproxy and nginx configuration files.
'''
 
 
# I M P O R T S ###############################################################
 
 
import github
import os
import sys
import time
import io
import logging
import traceback
import hashlib
import rsa_crypto
import argparse
import socket
import getpass
import base64
if (sys.version_info > (3, 0)):
    from urllib.parse import urljoin
else:
    from urlparse import urljoin
 
 
# G L O B A L S ###############################################################
 
file_list = ['/tmp/test.txt', '/etc/haproxy/haproxy.cfg', '/etc/nginx/nginx.conf']
 
# # 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 = ''
 
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 main():
    """Main function."""
    global logger
    global repo
    global args
 
    # Get the server name to use as a directory in GitHub
    server_name = socket.gethostname().split('.')[0].lower()
    # Get the username to log who made the change, nobody will be a Cron task or such
    try:
        user_name = getpass.getuser()
    except Exception as e:
        user_name = None
    if user_name is None:
        try:
            user_name = os.getlogin()
        except OSError as e:
            user_name = 'nobody'
        except Exception as e:
            user_name = 'unknown'
 
    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)
 
    for local_file_name in file_list:
        file_content = ''
        if os.path.exists(local_file_name):
            logger.info('File %s exists, processing.' % local_file_name)
            # Redacting HAproxy auth passwords, more may be needed for your use-case
            with io.open(local_file_name, "r", encoding="utf-8") as f:
                for line in f:
                    if 'auth' in line:
                        file_content += line[:line.find('auth')] + 'auth <REMOVED>\n'
                    else:
                        file_content += line
            # print(file_content)
 
            data = file_content.encode('utf-8', 'ignore')
            filesize = len(data)
            content = "blob " + str(filesize) + "\0" + data.decode('utf-8')
            encoded_content = content.encode('utf-8')
            localSHA = hashlib.sha1(encoded_content).hexdigest()
 
            remote_file_name = urljoin(remote_base_path, server_name + '/' + os.path.basename(local_file_name))
            logger.info(f"Saving local file {local_file_name} to remote GitHub repo {repo.full_name} file {remote_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}")
                remoteSHA = None
            except Exception as e:
                logger.error("Error {0}".format(str(e)))
                logger.error(traceback.format_exc())
                remoteSHA = None
 
            if remoteSHA == localSHA:
                logger.info('Remote file is present, hash is the same, NOT updating.')
                continue
            else:
                try:
                    if remoteSHA is None:
                        logger.info('Remote file file is NOT present, creating new file')
                        repo.create_file(remote_file_name, f"Updated by {user_name}", data, branch=ghe_branch)
                    else:
                        logger.info('Remote file file is present but hash has changed, updating file')
                        repo.update_file(remote_file_name, f"Updated by {user_name}", data, remoteSHA, branch=ghe_branch)
 
                except Exception as e:
                    logger.error("Error {0}".format(str(e)))
                    logger.error(traceback.format_exc())
                logger.info('Done updating GitHub')
 
        else:
            logger.warning('File does not exist %s' % local_file_name)
 
    logger.info('*** DONE ***')
    sys.exit(0)
 
###############################################################################
 
 
if __name__ == "__main__":
    main()
 
# E N D   O F   F I L E #######################################################
投稿コメント 0