python使用fabric之只看這篇就夠了

fabric 是什么

項目地址在: https://gitee.com/xiaofeipapa/fabric-tutorial

建議先下載代碼, 邊看邊運行.

fabric 是python的一個組件, 能夠在遠程服務器運行你寫好的腳本, 也就是說這是一個運維自動化工具.

以前運維人員在遠程服務器上安裝軟件, 要先登錄 -> 寫腳本(如果習慣本地寫程序, 那么還要上傳程序到服務器) -> 針對服務器反饋輸入數(shù)據(jù) -> 調(diào)試 ..

如果用fabric, 這個流程就是全部自動化了. fabric的程序開發(fā)流程一般是這樣:

  1. 先在遠程服務器試驗你的腳本/想法.
  2. 用python fabric 實現(xiàn)它們.
  3. 運行結(jié)果, 觀察是否正確.

如果只需要操作一兩臺服務器, 那么手動操作肯定比寫程序快. 如果要操作幾十上百臺機器, 那么當然是寫好程序, 然后讓他們自動在幾十臺服務器上面跑更方便. (程序員就是要學會偷懶! )

2021版教程說明

網(wǎng)上能找到的fabric 大多東摘西抄, 程序不能運行. 鍋主要由fabric 來背, 一開始他們并不支持 python3, 所以當python3 流行之后, 以前針對 python2 的fabric教程就基本不能用了; 于是有熱心程序員們搞了個非官方的fabric3 , 寫了一些教程(很少且很多文章是錯的), 但后來fabric 官方支持了 python3 , 也出了個教程....

于是在互聯(lián)網(wǎng)上至少存在3種fabric 教程. 針對python2的, 針對非官方python3版本的, 還有官方python3 官方版本的, 初學者大概會被弄得要發(fā)瘋了吧....

所以我將這份教程命名為2021版, 表示這是今年最新的版本. 同時這是針對官方python3版本的教材, 如果你不熟悉fabric, 就不用看網(wǎng)上其他教程了, 看這一篇即可.

最后, 這篇教程用的是 python3 的fabric 版本. 因此你本機要先安裝python3. 如果是linux 版本, 一般是這樣:

sudo apt install python3-pip

安裝完成之后, 只要用:

sudo pip3 install xxx

所安裝的包/組件就都是python3版本了.

如果是windows系統(tǒng), 假設你下載安裝的就是python3的軟件, 那么當你使用 pip install 的時候, 這個pip 就是3的版本. 所以不需要再多設置.

如何安裝

linux:

sudo pip3 install fabric

windows:

pip install fabric

實驗環(huán)境

如果你很熟悉docker 或者自己就有云服務器, 那么試驗環(huán)境就有了. 如果你沒有(特別是windows 用戶, 很難裝好docker) 怎么辦.

你可以使用我的這個虛擬機鏡像. 首先你要安裝 virtualbox, 下載地址在這里: https://www.virtualbox.org/wiki/Downloads

在某云盤下載我的這個虛擬機鏡像:
這是個 xubuntu 系統(tǒng)(ubuntu的變種), 我最愛的系統(tǒng)之一.

安裝之后用virtualbox 把它導入, 一步步操作如下:

打開virtualbox之后, 選擇New


image.png

Type選擇Linux, Version 選擇Ubuntu(64bit) , 然后點擊Next.

image.png

內(nèi)存默認 1024 就夠了, 點Next

image.png

選擇"Use an existing ... " , 然后點右邊那個文件夾圖標

在打開的界面中, 選擇 Add


image.png

然后選擇剛才下載的xub.vdi文件, 如圖:


image.png

然后點 choose


image.png

這時候你會看到 create 已經(jīng)變得可選了, 點它. 就會看到虛擬機已經(jīng)成功創(chuàng)建.

image.png

點擊右上方的Settings, 然后在彈出的界面設置網(wǎng)卡:


image.png

(中文名字應該是橋接網(wǎng)絡) . 這樣選了之后, 你的電腦(稱為宿主機) 就可以訪問虛擬機里的操作系統(tǒng)了.

雙擊xub 啟動系統(tǒng), 你會看到 xubuntu的界面. 這個系統(tǒng)有兩個用戶的信息, 分別是:
xiaofeipapa / 密碼 aa
root / 密碼 test

在xubuntu的桌面點擊鼠標右鍵, 選擇 Open Terminal Here, 打開命令行, 輸入:

ifconfig 

查看自己的ip是多少. 在我這里是 192.168.135.24 .

然后在宿主機里打開命令行, 輸入:

ssh root@192.168.135.24 

然后輸入root的密碼(test ) , 就可以看到ssh 登錄成功了. 好了, 你擁有一個實驗環(huán)境, 可以在上面試驗fabric了.

實驗環(huán)境信息總結(jié)

ip : 192.168.135.24 ( 也許你的機器會不同, 在虛擬機里用ifconfig來查看)
用戶: root / test

fabric 基礎

以下程序的參數(shù)都針對我那個虛擬機鏡像, 如果你有自己的虛擬機或者云服務器, 改相應參數(shù)即可.

起步: 連接服務器, 執(zhí)行命令

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  簡書: http://www.reibang.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由轉(zhuǎn)載此博客文章, 懇請保留原鏈接, 謝謝!
"""
from fabric import Connection


def do_it():

    host = '192.168.135.24'
    user = 'root'
    password = 'test'

    # ssh 連接的正確姿勢
    conn = Connection(host=host, user=user, connect_kwargs={'password': password})

    # 在遠程機器運行命令(用run方法), 并獲得返回結(jié)果
    # hide 表示隱藏遠程機器在控制臺的輸出, 達到靜默的效果
    # 默認 warn是False, 如果遠程機器運行命令出錯, 那么本地會拋出異常堆棧. 設為True 則不顯示這堆棧.
    cmd = 'ls /tmp'
    result = conn.run(cmd, hide=True, warn=True, encoding='utf-8')

    # 正常運行時, 信息在 stdout里
    print('-------- 下面是 stdout 信息')
    print(result.stdout.strip())

    # 出錯時, 信息在 stderr 里
    print('-------- 下面是 stderr 信息')
    print(result.stderr.strip())


if __name__ == '__main__':
    do_it()

注釋里已經(jīng)把api用法說得很清楚了. 請閱讀并運行程序加深印象.

封裝工具類 fab_utils.py

在我們寫程序的時候, 其實大部分時候都運行正常. 如果每次run之后都去判斷 stdout, stderr ... 代碼很快變得混亂不堪. 所以我的選擇是將之封裝成方法, 使之更加輕便. 這個文件命名為 fab_utils.py, 內(nèi)容如下:

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  簡書: http://www.reibang.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由轉(zhuǎn)載此博客文章, 懇請保留原鏈接, 謝謝!

  將fabric api 再封裝一層, 使之更好用
"""


class FabException(Exception):
    def __init__(self, msg):
        Exception.__init__(self, msg)


# 運行遠程命令
def run(conn, cmd, hide=True, warn=True):

    r = conn.run(cmd, encoding='utf8', hide=hide, warn=warn)

    result, err = r.stdout.strip(), r.stderr.strip()
    if err:
        raise FabException(err)

    return result

以后, 我會把這個工具庫逐漸擴大, 加入更多有用的方法. 經(jīng)過這種方式, fabric的大多數(shù)功能就變成你的工具庫, 可以輕松寫出大多數(shù)的運維程序.

寫一個程序 s2_test_fab_utils.py 來測試:

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  簡書: http://www.reibang.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由轉(zhuǎn)載此博客文章, 懇請保留原鏈接, 謝謝!
"""
from fabric import Connection
import fab_utils


def do_it():

    host = '192.168.135.24'
    user = 'root'
    password = 'test'

    # ssh 連接的正確姿勢
    conn = Connection(host=host, user=user, connect_kwargs={'password': password})

    # 結(jié)果變得更簡單了
    cmd = 'ls /tmp'
    result = fab_utils.run(conn, cmd)

    # 正常運行時, 信息在 stdout里
    print('-------- 下面是結(jié)果')
    print(result)

    # 出錯時, 程序會拋出異常
    # 來一個出錯的例子, tmp/xiaofeipapa 目錄不存在
    cmd = 'ls /tmp/xiaofeipapa'
    result = fab_utils.run(conn, cmd)
    print(result)


if __name__ == '__main__':
    do_it()

運行這個結(jié)果, 你能看到第一個例子成功輸出結(jié)果, 第二個例子拋出異常, 這就是我想要的效果.

ssh無密碼登錄ssh

登錄密碼寫在參數(shù)里當然不安全. 用linux 系統(tǒng)的同學都知道, 可以配置無密碼方式訪問服務器, 避免暴露密碼明文在參數(shù)里. (windows我暫時不知道怎么辦, 有待請教. )

首先在宿主機生成密鑰, 一路按回車:

ssh-keygen -t rsa

然后運行這個腳本:

ssh-copy-id root@192.168.135.24

輸入root的登錄密碼 test, 以后就可以無密碼訪問服務器了. 相應地, fabric的連接變成如下:

conn = Connection(host=host, user=user)

不用輸入密碼了.

運行自定義shell腳本

像ls, cd 都是bash 預先提供的命令. 如果要運行自己寫的shell怎么辦? 有兩個辦法: 如果sh 文件很大, 那么先在本地寫好, 然后傳到服務器上運行. 如果shell很少, 直接用這個方式運行:

conn.client.exec_command(cmd)

下面的這個例子演示了如何運行短的shell腳本:

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  簡書: http://www.reibang.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由轉(zhuǎn)載此博客文章, 懇請保留原鏈接, 謝謝!
"""
from fabric import Connection
import fab_utils


def do_it():

    host = '192.168.0.12'
    user = 'root'

    # ssh 連接的正確姿勢
    conn = Connection(host=host, user=user)

    # 運行shell之前, 要隨便運行一個命令, 獲得運行環(huán)境
    fab_utils.run(conn, 'uname -a')

    # 運行shell腳本
    # 在shell里 [ -d xxx ] 表示檢查文件夾是否存在
    # [ -d xxx ] 表示檢查文件是否存在
    cmd = '[ -d /tmp ] && echo ok'
    _stdin, _stdout, _stderr = conn.client.exec_command(cmd)
    result = _stdout.read().strip().decode('utf8')

    print('--- std out: ')
    print(result)


if __name__ == '__main__':
    do_it()


檢查文件/夾是否存在

bash沒有提供檢查文件夾是否存在, 文件是否存在的方法, 而這兩個方法是經(jīng)常需要使用的. 所以我們可以用簡單的shell腳本來封裝這兩個方法. 在 fab_utils.py 里加入如下方法:


# 檢查文件夾是否存在
def is_dir_exists(conn, dir_path):
    cmd = '[ -d ' + dir_path + ' ] && echo ok'
    _stdin, _stdout, _stderr = conn.client.exec_command(cmd)
    result = _stdout.read().strip().decode('utf8')

    return result == 'ok'


# 檢查文件文件是否存在
def is_file_exists(conn, file_path):
    cmd = '[ -f ' + file_path + ' ] && echo ok'
    _stdin, _stdout, _stderr = conn.client.exec_command(cmd)
    result = _stdout.read().strip().decode('utf8')

    return result == 'ok'


# 判斷是否有該文件夾, 不存在時可以通過參數(shù)控制生成文件夾
def check_has_dir(conn, dir_path, create_if_not_exist=True):

    if not is_dir_exists(conn, dir_path):
        if create_if_not_exist:
            run(conn, 'mkdir -p ' + dir_path)


# 判斷是否有該文件, 不存在時可以通過參數(shù)控制生成文件夾
def check_has_file(conn, file_path, create_if_not_exist=True):

    if not is_dir_exists(conn, file_path):
        if create_if_not_exist:
            run(conn, 'touch ' + file_path)

然后寫一個python文件來檢查:

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  簡書: http://www.reibang.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由轉(zhuǎn)載此博客文章, 懇請保留原鏈接, 謝謝!
"""
from fabric import Connection
import fab_utils
import os, os.path


def do_it():

    host = '192.168.0.12'
    user = 'root'

    # ssh 連接的正確姿勢
    conn = Connection(host=host, user=user)

    # 運行shell之前, 要隨便運行一個命令, 獲得運行環(huán)境
    fab_utils.run(conn, 'uname -a')

    # 檢查文件夾
    dir_path = '/root/data'
    fab_utils.check_has_dir(conn, dir_path)

    # 查看創(chuàng)建文件夾結(jié)果
    result = fab_utils.run(conn, 'ls ' + dir_path)
    print(result)

    # 檢查文件
    file_path = os.path.join(dir_path, 'done.txt')
    fab_utils.check_has_file(conn, file_path)

    # 查看創(chuàng)建文件結(jié)果
    result = fab_utils.run(conn, 'ls ' + file_path)
    print(result)


if __name__ == '__main__':
    do_it()

安全的刪除

我們都知道在服務器上盡量不要用rm命令, 否則刪錯文件那就完了. 所以在用fabric的時候我們同樣要注意這個問題. 在fab_utils.py 里加入這個方法:

# 在服務器安全刪除文件/文件夾的方法
def safe_rm(conn, file_path):

    # 先檢查是否存在
    try:
        run(conn, 'ls ' + file_path)
    except FabException:
        print(file_path + ' 不存在')
        return

    # 在/tmp下生成當天目錄
    result = run(conn, 'date +%Y-%m-%d')

    trash_dir = '/tmp/my_trash/' + result
    check_has_dir(conn, trash_dir)

    # 在當天下生成uuid, 確保mv 不會失敗
    result = run(conn, 'uuidgen')
    trash_dir = trash_dir + '/' + result
    check_has_dir(conn, trash_dir)

    run(conn, 'mv ' + file_path + ' ' + trash_dir)

寫一個文件來測試. 程序在 s5_rm_test.py

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  簡書: http://www.reibang.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由轉(zhuǎn)載此博客文章, 懇請保留原鏈接, 謝謝!
"""
from fabric import Connection
import fab_utils
import os, os.path


def do_it():

    host = '192.168.0.12'
    user = 'root'

    # ssh 連接的正確姿勢
    conn = Connection(host=host, user=user)

    # 運行shell之前, 要隨便運行一個命令, 獲得運行環(huán)境
    fab_utils.run(conn, 'uname -a')

    # 刪除之前創(chuàng)建的文件夾 /root/data
    dir_path = '/root/data'
    fab_utils.safe_rm(conn, dir_path)

    if fab_utils.is_dir_exists(conn, dir_path):
        print('--- 文件夾還在')
    else:
        print('--- 文件夾已經(jīng)刪除')


if __name__ == '__main__':
    do_it()

寫入文本文件

在服務器上我們通常會有寫文件的任務, 例如寫入環(huán)境變量到配置文件, 在fabric 里可以用簡單的shell命令來完成:

conn.run("echo 'hello' >> /tmp/test.txt")

這個命令又是雙引號又是單引號, 所以還是封裝起來更好用. 在fab_utils.py 里加入如下方法:

# 追加一行到文件末尾
def append_line(conn, file_path, content):
    if not is_file_exists(conn, file_path):
        return

    cmd = "echo '" + content + "' >> " + file_path
    run(conn, cmd)

然后寫一個文件來測試, 代碼在 s6_env_var.py

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  簡書: http://www.reibang.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由轉(zhuǎn)載此博客文章, 懇請保留原鏈接, 謝謝!
"""
from fabric import Connection
import fab_utils


def do_it():

    host = '192.168.0.12'
    user = 'root'

    # ssh 連接的正確姿勢
    conn = Connection(host=host, user=user)

    # 運行shell之前, 要隨便運行一個命令, 獲得運行環(huán)境
    fab_utils.run(conn, 'uname -a')

    # 在/tmp 下生成 test_rc 文件,
    file_path = '/root/test_rc'
    fab_utils.check_has_file(conn, file_path)

    # 寫入內(nèi)容到文件里

    fab_utils.append_line(conn, file_path, "#==其他配置信息1")
    fab_utils.append_line(conn, file_path, "#==其他配置信息2")
    fab_utils.append_line(conn, file_path, "#==其他配置信息3")
    fab_utils.append_line(conn, file_path, "\n")
    fab_utils.append_line(conn, file_path, "#==JAVA安裝信息")
    fab_utils.append_line(conn, file_path, "JAVA_HOME=XXX")
    fab_utils.append_line(conn, file_path, "CLASS_PATH=YYY")
    fab_utils.append_line(conn, file_path, "export PATH=$PATH:JAVA_HOME/bin")

    # 查看文件內(nèi)容
    result = fab_utils.run(conn, 'cat ' + file_path)
    print(result)


if __name__ == '__main__':
    do_it()

自動響應

有些命令如 passwd , adduser 會等候你繼續(xù)輸入下一步. 像這種需要手動響應的自動化程序應該怎么寫? 在fabric 里這樣做:


from invoke import Responder

# 其他代碼
resp1 = Responder(
        pattern=r'Enter new UNIX password:',
        response=pwd + '\n'
    )

    resp2 = Responder(
        pattern=r'Retype new UNIX password:',
        response=pwd + '\n'
    )

    conn.run('passwd ' + username, pty=True, hide=True, watchers=[resp1, resp2])

Responder 是 invoke 框架的一部分(fabric 已經(jīng)包括invoke框架) . 它的使用方法如下:

  1. pattern: 正則表達式, 用來匹配命令行的輸出. 如果里面有需要轉(zhuǎn)義的字符, 千萬要記得轉(zhuǎn)義.
  2. response: 需要輸入的內(nèi)容.
    這兩個參數(shù)結(jié)合起來的意思是: 如果匹配到命令輸出, 需要輸入信息的時候, 就自動輸入response定好的字符串.

如果不確定某些命令的pattern和resonse 是什么. 可以先在服務器手動輸入命令看看結(jié)果, 然后再寫程序. 如果程序多次需要輸入, 那么就要寫多個responder, 用個數(shù)組包起來.

注意: 如果程序長時間沒有反饋, 證明你的pattern 寫得有問題.

最常見的原因就是轉(zhuǎn)義字符沒有進行處理. 下面這是一個例子:


# 在yum上安裝軟件
def yum_install(conn, cmd):

    resp = Responder(
        # pattern=r'Is this ok [y/d/N]:',   # 錯誤例子: 沒有正則轉(zhuǎn)義
        pattern=r'Is this ok \[y/d/N\]:',
        response='y\n'
    )

    conn.run(cmd, pty=True, watchers=[resp], hide=True)

下面是一個完整的例子: 我們將用戶xiaofeipapa的密碼改為 bb, 整個過程是自動化無人工干預的:

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""
  作者: 小肥爬爬
  簡書: http://www.reibang.com/u/db796a501972
  gitee: https://gitee.com/xiaofeipapa/python-toolkit
  您可以自由轉(zhuǎn)載此博客文章, 懇請保留原鏈接, 謝謝!
"""
from fabric import Connection
import fab_utils
from invoke import Responder


def do_it():

    host = '192.168.0.12'
    user = 'root'

    # ssh 連接的正確姿勢
    conn = Connection(host=host, user=user)

    # 運行shell之前, 要隨便運行一個命令, 獲得運行環(huán)境
    fab_utils.run(conn, 'uname -a')

    # 將密碼改成bb
    username = 'xiaofeipapa'
    pwd = 'bb'
    resp1 = Responder(
        pattern=r'Enter new UNIX password:',
        response=pwd + '\n'
    )

    resp2 = Responder(
        pattern=r'Retype new UNIX password:',
        response=pwd + '\n'
    )

    conn.run('passwd ' + username, pty=True, hide=True, watchers=[resp1, resp2])


if __name__ == '__main__':
    do_it()

fabric 實戰(zhàn)案例: 在遠程服務器上安裝 jdk環(huán)境

前面零零散散地介紹了很多fabric的用法, 現(xiàn)在用這個案例把它綜合起來: 在遠程服務器(虛擬機) 里安裝jdk環(huán)境.

這個任務做開發(fā)/運維的同學應該都不陌生. java技術棧的運維架構通常是1個nginx, 后端掛若干個安裝jdk服務器, 以此達到負載均衡的目的.它的任務可以分解為:

  1. 在遠程服務器上下載解壓jdk包.
  2. 在配置文件寫入若干個環(huán)境變量(如JAVA_HOME, CLASSPATH) , 指向jdk的解壓路徑.
  3. 創(chuàng)建新的登錄用戶, 開發(fā)者用它來登錄機器, 發(fā)布jar 包.

當然其中還包括一些彎彎繞繞的必要任務. 例如怎么判斷任務已經(jīng)運行過, 所以不會再重復運行? 怎么判斷jdk包已經(jīng)存在, 不用再重新下載? 怎么判斷環(huán)境變量已經(jīng)寫入到配置文件, 不用重復寫入?

這些技巧一一列舉如下.

怎么知道任務是否第一次執(zhí)行?

思路: 給任務一個名字, 例如 install_jdk. 第一次在目標機器運行之后, 就會在 /root 目錄下生成這個文件: /root/data/install_jdk/done.txt . 所以程序可以判斷這個文件是否存在, 如果不存在表示此任務還沒執(zhí)行過. 如果存在, 那么可以通過命令行參數(shù)來指定是否再次運行任務.

代碼如下:


    def _prepare(self, rerun):
        """
        檢查任務是否已經(jīng)啟動過
        :return:
        """
        # 必須要先run連接遠程服務器, 否則 conn.client.exec_command 會報錯
        fab_utils.run(self.conn, 'ls /tmp')

        # 生成 /root/data 目錄
        base_dir = '/root/data'
        fab_utils.create_if_remote_dir_not_exist(self.conn, base_dir)

        # 檢查該task 是否已經(jīng)執(zhí)行過. 如果已經(jīng)執(zhí)行過, 在 /root/data/task 目錄下有 done.txt 文件
        task_home = base_dir + '/' + self.task
        fab_utils.create_if_remote_dir_not_exist(self.conn, task_home)
        self.task_home = task_home

        task_file = task_home + '/done.txt'
        if not fab_utils.is_remote_file_exist(self.conn, task_file):
            return

        # 如果存在, 又不指定重新運行, 則退出
        if not rerun:
            title('此程序已經(jīng)在目標機器運行過了. 如果想再次運行, 請設置啟動參數(shù) -r true')
            exit(0)

# ...... 其他代碼
# 命令行指定啟動參數(shù)

if __name__ == '__main__':

    import argparse

    parser = argparse.ArgumentParser('傳入?yún)?shù):***.py')
    parser.add_argument('-r', '--rerun', default=True)    # 開發(fā)時用
    # parser.add_argument('-r', '--rerun', default=False)

    args = parser.parse_args()

    rerun = args.rerun

    print('---- 參數(shù) rerun: ', rerun)

    do_it(args)

判斷jdk存在,不用重復下載

思路: 當?shù)谝淮蜗螺d好jdk包的時候, 我們可以拿到該包的md5, 然后如果服務器上已經(jīng)存在jdk包并且md5一致, 那么就不用再下載. 否則開始下載jdk.

代碼如下:


        with self.conn.cd(self.task_home):

            # 檢查下載jdk
            file_path = self.task_home + '/' + jdk_file
            file_md5 = 'ef599e322eee42f6769991dd3e3b1a31'
            down_link = 'wget https://repo.huaweicloud.com/java/jdk/8u181-b13/' + jdk_file
            fab_utils.check_download(self.conn, file_path, file_md5, down_link)

            # 創(chuàng)建jvm目錄
            fab_utils.create_if_remote_dir_not_exist(self.conn, jvm_dir)

            # 解壓. 華為的這個包解壓后名字不同, 所以手動指定一個目錄
            hint('正在解壓 jdk ......')
            fab_utils.run(self.conn, 'tar xvf ' + jdk_file + ' -C ' + jvm_dir + '  --strip-components 1 ')

寫入配置文件與重復檢查

有兩個思路:

  1. 利用sed, 如果發(fā)現(xiàn)已經(jīng)有相應的環(huán)境變量, 將之刪除重新寫入.
  2. 用 echo 顯示 JAVA_HOME, 如果有值

第一個思路容易錯刪其他內(nèi)容, 所以后來采用了第二個思路. 代碼如下:


        run_result = conn.run('echo $JAVA_HOME ', hide=True).stdout.strip()
        if run_result == jvm_dir:
            hint(system_file + ' 已經(jīng)包含 JAVA_HOME, 不需要重新設置')
        else:
            # 追加內(nèi)容到系統(tǒng)配置
            conn.run("echo '# ===== java 配置' >> " + system_file)
            conn.run("echo 'export JAVA_HOME=" + jvm_dir + "' >> " + system_file)

            # 將 exporter jar 也加入到 classpath 里
            export = 'export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/jre/lib/rt.jar:'\
                     + exporter_jar_path
            conn.run("echo '" + export + "' >> " + system_file)
            conn.run("echo 'export PATH=$PATH:$JAVA_HOME/bin' >> " + system_file)

            # 使環(huán)境變量生效
            conn.run('source ' + system_file)

這里還有個小竅門, linux 那么多配置文件如 /etc/profile , /etc/bashrc , 究竟應該寫到哪個文件? 查了一下資料, /etc/profile 只在用戶第一次登錄的時候起作用, 所以應該寫到 /etc/bashrc 里.

怎么生成新用戶

思路: 生成新用戶很簡單, 需要考慮的是給它設定密碼. 考察了一下linux上生成密碼的方式, 最后決定用 pwgen . 用法:

# 生成10個字符的密碼, 個數(shù)為1
sudo apt install pwgen 
pwgen -s 10 1

代碼大概長這樣:

    def add_user(self, username):
        """
        增加用戶, 并設置密碼.
        :param username: 用戶名
        :return: 登錄密碼
        """

        if self._is_user_exist(username):
            title('用戶 ' + username + ' 已經(jīng)存在')
            fab_utils.close_connection(self.conn)
            return None

        # 生成用戶. 該命令只在centos 測試過
        fab_utils.run(self.conn, 'adduser ' + username)

        # 生成10位密碼
        cmd = 'pwgen -s 10 1'
        password = fab_utils.run(self.conn, cmd)

        # 更改密碼
        fab_utils.yum_passwd(self.conn, username, password)

        # 退出登錄
        fab_utils.close_connection(self.conn)

        # 登錄進行測試
        self.verify_login(username, self.host, password)

        return password

完整代碼

項目地址: https://gitee.com/xiaofeipapa/fabric-tutorial

請看 setup_java.py 文件

總結(jié)

通過這么多方法和整個案例的展示, 你應該掌握了fabric 90%的用法, 剩下的就靠你自己探索了. 希望你用fabric玩得愉快~

記得給個star和贊啊, 少年~

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市庶溶,隨后出現(xiàn)的幾起案子姿鸿,更是在濱河造成了極大的恐慌洒琢,老刑警劉巖恭理,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件替久,死亡現(xiàn)場離奇詭異鹃锈,居然都是意外死亡误甚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門柒桑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弊决,“玉大人,你說我怎么就攤上這事魁淳∑” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵界逛,是天一觀的道長昆稿。 經(jīng)常有香客問我,道長仇奶,這世上最難降的妖魔是什么貌嫡? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任比驻,我火速辦了婚禮,結(jié)果婚禮上岛抄,老公的妹妹穿的比我還像新娘别惦。我一直安慰自己,他們只是感情好夫椭,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布掸掸。 她就那樣靜靜地躺著,像睡著了一般蹭秋。 火紅的嫁衣襯著肌膚如雪扰付。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天仁讨,我揣著相機與錄音羽莺,去河邊找鬼。 笑死洞豁,一個胖子當著我的面吹牛盐固,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播丈挟,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼刁卜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了曙咽?” 一聲冷哼從身側(cè)響起蛔趴,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎例朱,沒想到半個月后孝情,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡茉继,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年咧叭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烁竭。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖吉挣,靈堂內(nèi)的尸體忽然破棺而出派撕,到底是詐尸還是另有隱情,我是刑警寧澤睬魂,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布终吼,位于F島的核電站,受9級特大地震影響氯哮,放射性物質(zhì)發(fā)生泄漏际跪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望姆打。 院中可真熱鬧良姆,春花似錦、人聲如沸幔戏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闲延。三九已至痊剖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間垒玲,已是汗流浹背陆馁。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留合愈,地道東北人氮惯。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像想暗,于是被迫代替她去往敵國和親妇汗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容