やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

sshのconfigファイル編集するPythonコードについて考えてみた

ライブラリがありそうだが、ファイル編集するようなものは見つけられなかった。

やりたいこと

GitHubでPushできるようにするためのSSH設定を自動化したい。

しかし、~/.ssh/configファイル編集が曲者。簡単なツールやライブラリが見当たらない。

  • ~/.ssh/configファイルを編集するクラスSshConfigurator.pyを作成したい
    • ~/.ssh/configファイルからPythonのdict型データを取得したい
    • Pythonのdict型データでconfigファイルを編集したい
      • ~/.ssh/configファイルの末尾にデータを追記したい
      • ~/.ssh/configファイルの指定位置にデータを挿入したい
      • ~/.ssh/configファイルのデータを変更したい
      • ~/.ssh/configファイルからデータを削除したい
    • 共有ファイルなので壊さないようにする
      • コメント行には関与しない
      • ツールが管理するGitHubアカウントの設定以外は関与しない

問題

~/.ssh/configファイルはシステム共有設定である。

今回、GitHubアカウント設定以外にもすでに設定があるかもしれない。それらを邪魔しないようにファイルを編集することが難しい。

Pythonインタフェース

import SshConfigurator
c = SshConfigurator.SshConfigurator()

読み込み

  • c.Hosts # {‘some_host’: {…}, ‘some_host2’: {…}, …}
  • c.Hosts[‘some_host’] # {‘Host’: ‘some_host’, ‘Port’: 22, …}
  • c.Hosts[‘some_host’][‘Host’] # ‘some_host’
  • c.Hosts[‘some_host’][‘Port’] # 22

書き込み

追記

ファイルの末尾に追記する。

import SshConfigurator
host = {}
host['Host'] = 'github.com.{user}'
host['User'] = 'git'
host['Port'] = 22
host['HostName'] = 'github.com'
host['IdentityFile'] = '~/.ssh/rsa_{user}'
host['TCPKeepAlive'] = 'yes'
host['IdentitiesOnly'] = 'yes'
c = SshConfigurator.SshConfigurator.Host()
c.appendHost(host)

編集

import SshConfigurator
c = SshConfigurator.SshConfigurator()
c.Hosts['github.com.{user111}']['Port'] = 33
c.Hosts['github.com.{user222}']['Port'] = 44
c.modifyHost([c.Hosts['github.com.{user111}'], c.Hosts['github.com.{user222}'], ])

削除

import SshConfigurator
c = SshConfigurator.SshConfigurator()
c.deleteHost(['github.com.{user111}', 'github.com.{user222}'])

問題

ファイル内の設定順序を保とうとすると一気に難しくなる。

Pythonで順序付きdict型はOrderedDictを使えばできそう。

http://shannon-lab.org/?p=1743

しかし、問題はconfigファイルの内容はHostだけでなくコメント行やHost外定義も含まれる点。一体どうやってファイル位置を保ったまま、Hostの編集、削除、挿入すればいいのか。

やはり管理するアカウントの設定部分のみ別ファイルにし、あとで追記する形がよいか。そうすれば複数行マッチで定義部分を抜き出すこともできる。そういう管理スクリプトなどを書いている人もいるらしい。

~/.ssh/configについて - Qiita

ならば順序に関しては関与しないほうがいいか。

  • ファイル内順序には関与しない
    • ユーザが手で管理したい場合があるから
    • ユーザが自作ツールなどで管理している場合があるから

そして、GitHubアカウント管理ツールで作成したconfig部分は別ファイルとして出力しておく。configマージツールを使っている場合に利用する。

結論

ファイル順序には関与しない。末尾の追記、編集、削除のみ行う。

削除が難しそうだが、複数行マッチング置換で解決する。

削除

Python: 正規表現で複数行マッチングの置換を行う - 無粋な日々に

手抜き案

追記はすでに実装した。

  • configの編集、削除は行わないようにする
  • データの取得はparamikoかfabricを使う

これで何もコードを書かなくてよくなる。

paramikoインストール

https://github.com/paramiko/paramiko

LGPLライセンス。

$ sudo pip3 install paramiko
Downloading/unpacking paramiko
  Downloading paramiko-2.1.2-py2.py3-none-any.whl (172kB): 172kB downloaded
Downloading/unpacking pyasn1>=0.1.7 (from paramiko)
  Downloading pyasn1-0.2.3-py2.py3-none-any.whl (53kB): 53kB downloaded
Downloading/unpacking cryptography>=1.1 (from paramiko)
  Downloading cryptography-1.8.1.tar.gz (423kB): 423kB downloaded
  Running setup.py (path:/tmp/pip_build_root/cryptography/setup.py) egg_info for package cryptography
    
    no previously-included directories found matching 'docs/_build'
    warning: no previously-included files matching '*' found under directory 'vectors'
Downloading/unpacking idna>=2.1 (from cryptography>=1.1->paramiko)
  Downloading idna-2.5-py2.py3-none-any.whl (55kB): 55kB downloaded
Downloading/unpacking asn1crypto>=0.21.0 (from cryptography>=1.1->paramiko)
  Downloading asn1crypto-0.22.0-py2.py3-none-any.whl (97kB): 97kB downloaded
Downloading/unpacking packaging (from cryptography>=1.1->paramiko)
  Downloading packaging-16.8-py2.py3-none-any.whl
Requirement already satisfied (use --upgrade to upgrade): six>=1.4.1 in /usr/local/lib/python3.4/dist-packages (from cryptography>=1.1->paramiko)
Downloading/unpacking setuptools>=11.3 (from cryptography>=1.1->paramiko)
  Downloading setuptools-34.3.3-py2.py3-none-any.whl (389kB): 389kB downloaded
Downloading/unpacking cffi>=1.4.1 (from cryptography>=1.1->paramiko)
  Downloading cffi-1.10.0.tar.gz (418kB): 418kB downloaded
  Running setup.py (path:/tmp/pip_build_root/cffi/setup.py) egg_info for package cffi
    Package libffi was not found in the pkg-config search path.
    Perhaps you should add the directory containing `libffi.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'libffi' found
    Package libffi was not found in the pkg-config search path.
    Perhaps you should add the directory containing `libffi.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'libffi' found
    Package libffi was not found in the pkg-config search path.
    Perhaps you should add the directory containing `libffi.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'libffi' found
    Package libffi was not found in the pkg-config search path.
    Perhaps you should add the directory containing `libffi.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'libffi' found
    Package libffi was not found in the pkg-config search path.
    Perhaps you should add the directory containing `libffi.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'libffi' found
    
Downloading/unpacking pyparsing (from packaging->cryptography>=1.1->paramiko)
  Downloading pyparsing-2.2.0-py2.py3-none-any.whl (56kB): 56kB downloaded
Downloading/unpacking appdirs>=1.4.0 (from setuptools>=11.3->cryptography>=1.1->paramiko)
  Downloading appdirs-1.4.3-py2.py3-none-any.whl
Downloading/unpacking pycparser (from cffi>=1.4.1->cryptography>=1.1->paramiko)
  Downloading pycparser-2.17.tar.gz (231kB): 231kB downloaded
  Running setup.py (path:/tmp/pip_build_root/pycparser/setup.py) egg_info for package pycparser
    
    warning: no previously-included files matching 'yacctab.*' found under directory 'tests'
    warning: no previously-included files matching 'lextab.*' found under directory 'tests'
    warning: no previously-included files matching 'yacctab.*' found under directory 'examples'
    warning: no previously-included files matching 'lextab.*' found under directory 'examples'
Installing collected packages: paramiko, pyasn1, cryptography, idna, asn1crypto, packaging, setuptools, cffi, pyparsing, appdirs, pycparser
  Running setup.py install for cryptography
    Package libffi was not found in the pkg-config search path.
    Perhaps you should add the directory containing `libffi.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'libffi' found
    Package libffi was not found in the pkg-config search path.
    Perhaps you should add the directory containing `libffi.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'libffi' found
    Package libffi was not found in the pkg-config search path.
    Perhaps you should add the directory containing `libffi.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'libffi' found
    Package libffi was not found in the pkg-config search path.
    Perhaps you should add the directory containing `libffi.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'libffi' found
    Package libffi was not found in the pkg-config search path.
    Perhaps you should add the directory containing `libffi.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'libffi' found
    c/_cffi_backend.c:15:17: fatal error: ffi.h: そのようなファイルやディレクトリはありません
     #include <ffi.h>
                     ^
    compilation terminated.
    Traceback (most recent call last):
      File "/usr/lib/python3.4/distutils/unixccompiler.py", line 116, in _compile
        extra_postargs)
      File "/usr/lib/python3.4/distutils/ccompiler.py", line 909, in spawn
        spawn(cmd, dry_run=self.dry_run)
      File "/usr/lib/python3.4/distutils/spawn.py", line 36, in spawn
        _spawn_posix(cmd, search_path, dry_run=dry_run)
      File "/usr/lib/python3.4/distutils/spawn.py", line 162, in _spawn_posix
        % (cmd, exit_status))
    distutils.errors.DistutilsExecError: command 'i686-linux-gnu-gcc' failed with exit status 1
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/usr/lib/python3.4/distutils/core.py", line 148, in setup
        dist.run_commands()
      File "/usr/lib/python3.4/distutils/dist.py", line 955, in run_commands
        self.run_command(cmd)
      File "/usr/lib/python3.4/distutils/dist.py", line 974, in run_command
        cmd_obj.run()
      File "/usr/lib/python3/dist-packages/setuptools/command/bdist_egg.py", line 185, in run
        cmd = self.call_command('install_lib', warn_dir=0)
      File "/usr/lib/python3/dist-packages/setuptools/command/bdist_egg.py", line 171, in call_command
        self.run_command(cmdname)
      File "/usr/lib/python3.4/distutils/cmd.py", line 313, in run_command
        self.distribution.run_command(command)
      File "/usr/lib/python3.4/distutils/dist.py", line 974, in run_command
        cmd_obj.run()
      File "/usr/lib/python3/dist-packages/setuptools/command/install_lib.py", line 21, in run
        self.build()
      File "/usr/lib/python3.4/distutils/command/install_lib.py", line 109, in build
        self.run_command('build_ext')
      File "/usr/lib/python3.4/distutils/cmd.py", line 313, in run_command
        self.distribution.run_command(command)
      File "/usr/lib/python3.4/distutils/dist.py", line 974, in run_command
        cmd_obj.run()
      File "/usr/lib/python3/dist-packages/setuptools/command/build_ext.py", line 49, in run
        _build_ext.run(self)
      File "/usr/lib/python3.4/distutils/command/build_ext.py", line 339, in run
        self.build_extensions()
      File "/usr/lib/python3.4/distutils/command/build_ext.py", line 448, in build_extensions
        self.build_extension(ext)
      File "/usr/lib/python3/dist-packages/setuptools/command/build_ext.py", line 178, in build_extension
        _build_ext.build_extension(self,ext)
      File "/usr/lib/python3.4/distutils/command/build_ext.py", line 503, in build_extension
        depends=ext.depends)
      File "/usr/lib/python3.4/distutils/ccompiler.py", line 574, in compile
        self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
      File "/usr/lib/python3.4/distutils/unixccompiler.py", line 118, in _compile
        raise CompileError(msg)
    distutils.errors.CompileError: command 'i686-linux-gnu-gcc' failed with exit status 1
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 1025, in run_setup
        run_setup(setup_script, args)
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 50, in run_setup
        lambda: execfile(
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 100, in run
        return func()
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 52, in <lambda>
        {'__file__':setup_script, '__name__':'__main__'}
      File "/usr/lib/python3/dist-packages/setuptools/compat.py", line 78, in execfile
        exec(compile(source, fn, 'exec'), globs, locs)
      File "setup.py", line 232, in <module>
    
      File "/usr/lib/python3.4/distutils/core.py", line 163, in setup
        raise SystemExit("error: " + str(msg))
    SystemExit: error: command 'i686-linux-gnu-gcc' failed with exit status 1
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip_build_root/cryptography/setup.py", line 335, in <module>
        **keywords_with_side_effects(sys.argv)
      File "/usr/lib/python3.4/distutils/core.py", line 108, in setup
        _setup_distribution = dist = klass(attrs)
      File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 239, in __init__
        self.fetch_build_eggs(attrs.pop('setup_requires'))
      File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 264, in fetch_build_eggs
        replace_conflicting=True
      File "/usr/lib/python3/dist-packages/pkg_resources.py", line 620, in resolve
        dist = best[req.key] = env.best_match(req, ws, installer)
      File "/usr/lib/python3/dist-packages/pkg_resources.py", line 858, in best_match
        return self.obtain(req, installer) # try and download/install
      File "/usr/lib/python3/dist-packages/pkg_resources.py", line 870, in obtain
        return installer(requirement)
      File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 314, in fetch_build_egg
        return cmd.easy_install(req)
      File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 616, in easy_install
        return self.install_item(spec, dist.location, tmpdir, deps)
      File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 646, in install_item
        dists = self.install_eggs(spec, download, tmpdir)
      File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 834, in install_eggs
        return self.build_and_install(setup_script, setup_base)
      File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 1040, in build_and_install
        self.run_setup(setup_script, setup_base, args)
      File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 1028, in run_setup
        raise DistutilsError("Setup script exited with %s" % (v.args[0],))
    distutils.errors.DistutilsError: Setup script exited with error: command 'i686-linux-gnu-gcc' failed with exit status 1
    Complete output from command /usr/bin/python3 -c "import setuptools, tokenize;__file__='/tmp/pip_build_root/cryptography/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-w6pgmxjz-record/install-record.txt --single-version-externally-managed --compile:
    Package libffi was not found in the pkg-config search path.

Perhaps you should add the directory containing `libffi.pc'

to the PKG_CONFIG_PATH environment variable

No package 'libffi' found

Package libffi was not found in the pkg-config search path.

Perhaps you should add the directory containing `libffi.pc'

to the PKG_CONFIG_PATH environment variable

No package 'libffi' found

Package libffi was not found in the pkg-config search path.

Perhaps you should add the directory containing `libffi.pc'

to the PKG_CONFIG_PATH environment variable

No package 'libffi' found

Package libffi was not found in the pkg-config search path.

Perhaps you should add the directory containing `libffi.pc'

to the PKG_CONFIG_PATH environment variable

No package 'libffi' found

Package libffi was not found in the pkg-config search path.

Perhaps you should add the directory containing `libffi.pc'

to the PKG_CONFIG_PATH environment variable

No package 'libffi' found

c/_cffi_backend.c:15:17: fatal error: ffi.h: そのようなファイルやディレクトリはありません

 #include <ffi.h>

                 ^

compilation terminated.

Traceback (most recent call last):

  File "/usr/lib/python3.4/distutils/unixccompiler.py", line 116, in _compile

    extra_postargs)

  File "/usr/lib/python3.4/distutils/ccompiler.py", line 909, in spawn

    spawn(cmd, dry_run=self.dry_run)

  File "/usr/lib/python3.4/distutils/spawn.py", line 36, in spawn

    _spawn_posix(cmd, search_path, dry_run=dry_run)

  File "/usr/lib/python3.4/distutils/spawn.py", line 162, in _spawn_posix

    % (cmd, exit_status))

distutils.errors.DistutilsExecError: command 'i686-linux-gnu-gcc' failed with exit status 1



During handling of the above exception, another exception occurred:



Traceback (most recent call last):

  File "/usr/lib/python3.4/distutils/core.py", line 148, in setup

    dist.run_commands()

  File "/usr/lib/python3.4/distutils/dist.py", line 955, in run_commands

    self.run_command(cmd)

  File "/usr/lib/python3.4/distutils/dist.py", line 974, in run_command

    cmd_obj.run()

  File "/usr/lib/python3/dist-packages/setuptools/command/bdist_egg.py", line 185, in run

    cmd = self.call_command('install_lib', warn_dir=0)

  File "/usr/lib/python3/dist-packages/setuptools/command/bdist_egg.py", line 171, in call_command

    self.run_command(cmdname)

  File "/usr/lib/python3.4/distutils/cmd.py", line 313, in run_command

    self.distribution.run_command(command)

  File "/usr/lib/python3.4/distutils/dist.py", line 974, in run_command

    cmd_obj.run()

  File "/usr/lib/python3/dist-packages/setuptools/command/install_lib.py", line 21, in run

    self.build()

  File "/usr/lib/python3.4/distutils/command/install_lib.py", line 109, in build

    self.run_command('build_ext')

  File "/usr/lib/python3.4/distutils/cmd.py", line 313, in run_command

    self.distribution.run_command(command)

  File "/usr/lib/python3.4/distutils/dist.py", line 974, in run_command

    cmd_obj.run()

  File "/usr/lib/python3/dist-packages/setuptools/command/build_ext.py", line 49, in run

    _build_ext.run(self)

  File "/usr/lib/python3.4/distutils/command/build_ext.py", line 339, in run

    self.build_extensions()

  File "/usr/lib/python3.4/distutils/command/build_ext.py", line 448, in build_extensions

    self.build_extension(ext)

  File "/usr/lib/python3/dist-packages/setuptools/command/build_ext.py", line 178, in build_extension

    _build_ext.build_extension(self,ext)

  File "/usr/lib/python3.4/distutils/command/build_ext.py", line 503, in build_extension

    depends=ext.depends)

  File "/usr/lib/python3.4/distutils/ccompiler.py", line 574, in compile

    self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)

  File "/usr/lib/python3.4/distutils/unixccompiler.py", line 118, in _compile

    raise CompileError(msg)

distutils.errors.CompileError: command 'i686-linux-gnu-gcc' failed with exit status 1



During handling of the above exception, another exception occurred:



Traceback (most recent call last):

  File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 1025, in run_setup

    run_setup(setup_script, args)

  File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 50, in run_setup

    lambda: execfile(

  File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 100, in run

    return func()

  File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 52, in <lambda>

    {'__file__':setup_script, '__name__':'__main__'}

  File "/usr/lib/python3/dist-packages/setuptools/compat.py", line 78, in execfile

    exec(compile(source, fn, 'exec'), globs, locs)

  File "setup.py", line 232, in <module>



  File "/usr/lib/python3.4/distutils/core.py", line 163, in setup

    raise SystemExit("error: " + str(msg))

SystemExit: error: command 'i686-linux-gnu-gcc' failed with exit status 1



During handling of the above exception, another exception occurred:



Traceback (most recent call last):

  File "<string>", line 1, in <module>

  File "/tmp/pip_build_root/cryptography/setup.py", line 335, in <module>

    **keywords_with_side_effects(sys.argv)

  File "/usr/lib/python3.4/distutils/core.py", line 108, in setup

    _setup_distribution = dist = klass(attrs)

  File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 239, in __init__

    self.fetch_build_eggs(attrs.pop('setup_requires'))

  File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 264, in fetch_build_eggs

    replace_conflicting=True

  File "/usr/lib/python3/dist-packages/pkg_resources.py", line 620, in resolve

    dist = best[req.key] = env.best_match(req, ws, installer)

  File "/usr/lib/python3/dist-packages/pkg_resources.py", line 858, in best_match

    return self.obtain(req, installer) # try and download/install

  File "/usr/lib/python3/dist-packages/pkg_resources.py", line 870, in obtain

    return installer(requirement)

  File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 314, in fetch_build_egg

    return cmd.easy_install(req)

  File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 616, in easy_install

    return self.install_item(spec, dist.location, tmpdir, deps)

  File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 646, in install_item

    dists = self.install_eggs(spec, download, tmpdir)

  File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 834, in install_eggs

    return self.build_and_install(setup_script, setup_base)

  File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 1040, in build_and_install

    self.run_setup(setup_script, setup_base, args)

  File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 1028, in run_setup

    raise DistutilsError("Setup script exited with %s" % (v.args[0],))

distutils.errors.DistutilsError: Setup script exited with error: command 'i686-linux-gnu-gcc' failed with exit status 1

----------------------------------------
Cleaning up...
Command /usr/bin/python3 -c "import setuptools, tokenize;__file__='/tmp/pip_build_root/cryptography/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-w6pgmxjz-record/install-record.txt --single-version-externally-managed --compile failed with error code 1 in /tmp/pip_build_root/cryptography
Storing debug log for failure in /home/mint/.pip/pip.log

インストールに失敗しているっぽい。

distutils.errors.DistutilsError: Setup script exited with error: command 'i686-linux-gnu-gcc' failed with exit status 1

paramikoをimportしようとするとエラーが出る。

import paramiko
ImportError: No module named 'cryptography'

依存関係を解決してくれないパッケージ管理マネージャpip…。

何をすればいいのかわからない。諦めよう。

所感

本題とは関係ないところなので乗り気ではない。iniファイルだったら簡単に編集できたのに…。