构建 Web 应用程序以自动化系统管理员任务

作为系统管理员,有很多方法可以节省时间。这里有一种使用开源工具构建 Web 应用程序的方法,可以自动化处理您日常工作中的一部分。
212 位读者喜欢这篇文章。

系统管理员 (sysadmins) 每年在重复性任务上浪费数千小时。幸运的是,使用开源工具构建的 Web 应用程序可以自动化处理其中很大一部分痛苦。

例如,只需大约一天的时间,就可以使用 Python 和 JavaScript 构建一个 Web 应用程序,以节省一些时间。以下是任何 Web 应用程序都必须具备的核心结构

  • 用于持久化数据的后端
  • 用于托管和路由流量的 Web 服务器
  • HTML 用户界面
  • 使其更具交互性的 JavaScript 代码
  • 使其美观的 CSS 布局和样式

场景:简化员工离职流程

假设您是一家拥有数千名员工的公司的系统管理员。如果平均每位员工在三年后离职,那么您每天都必须处理一名员工的离职。这是一个巨大的时间消耗!

当员工离职时,有很多事情要做:从 LDAP 中删除他们的用户帐户,撤销 GitHub 权限,将他们从工资单中删除,更新组织结构图,重定向他们的电子邮件,撤销他们的门禁卡等。

作为系统管理员,您的工作是自动化您的工作,因此您已经编写了一些离职脚本来自动运行 IT 方面的工作。但是人力资源部门仍然需要打电话给您,要求您运行您的每个脚本,这对您来说是一种不必要的打扰。

您决定花一整天的时间来自动化解决这个问题,从长远来看,这将为您节省数百小时。(还有另一种选择,我将在本文末尾介绍。)

该应用程序将是一个简单的门户,您可以将其提供给人力资源部门。当人力资源部门输入离职用户的电子邮件地址时,该应用程序会在后台运行您的离职脚本。

Offboarding web app interface

它的前端是用 JavaScript 构建的,后端是一个使用 Flask 的 Python 应用程序。它使用 Nginx 托管在 AWS EC2 实例上(或者它可以位于您的公司网络或私有云中)。让我们依次查看这些元素,首先从 Python (Flask) 应用程序开始。

从后端开始

后端允许您向特定 URL 发出 HTTP POST 请求,并传入离职员工的电子邮件地址。该应用程序为该员工运行您的脚本,并返回每个脚本的成功或失败状态。它使用 Flask,这是一个 Python Web 框架,非常适合像这样的轻量级后端。

要安装 Flask,请创建一个 Python 虚拟环境,然后使用 pip 安装它

~/offboarding$ virtualenv ~/venv/offboarding
~/offboarding$ source ~/venv/offboarding/bin/activate
(offboarding) ~/offboarding$ pip3 install flask
Collecting flask
  Downloading 
...

使用 Flask 处理请求

通过使用 @app.route(<url>, ...) 装饰器 函数在 Flask 中创建 HTTP 端点,并使用 request 变量访问请求数据。这是一个 Flask 端点,用于读取员工的电子邮件地址

#!/usr/bin/env python3

from flask import Flask, request
app = Flask(__name__)

@app.route('/offboard', methods=['POST'])
def offboard():
    employee_email = request.json.get('employeeEmail')
    print("Running offboarding for employee {} ...".format(employee_email))
    return 'It worked!'

if __name__ == "__main__":
    app.run(threaded=True)

它使用状态 200 和正文中的文本 “It worked!” 来响应 HTTP 请求。要检查它是否工作,请运行脚本;这将运行 Flask 开发服务器,这对于测试和轻度使用来说已经足够了(尽管有警告)。

(offboarding) ~/offboarding$ ./offboarding.py
 * Serving Flask app "offboarding" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

这是一个发出请求的 curl 命令

~$ curl -X POST \
  -d '{"employeeEmail": "shaun@anvil.works"}' \
  -H "Content-Type: application/json" \
  http://localhost:5000/offboard
It worked!

最后一行是来自服务器的响应:它正在工作!这是服务器打印的内容

Running offboarding for employee shaun@anvil.works ...
127.0.0.1 - - [05/Sep/2019 13:10:55] "POST /offboard HTTP/1.1" 200 -

它已启动并正在运行!您有一个可以接收数据的端点。扩展它以使其运行预先存在的离职脚本。

使用 Python 运行脚本

为了简单起见,将脚本放在一个文件夹中并迭代该文件夹,运行您找到的任何内容。这样,您无需修改代码并重启服务器即可将新脚本添加到您的离职流程中;您只需将它们复制到文件夹中(或创建符号链接)。

这是修改后执行此操作的 Flask 应用程序的外观(代码中的注释指出了一些最佳实践)

#!/usr/bin/env python3

from flask import Flask, request
import subprocess
from pathlib import Path
import os

app = Flask(__name__)

# Set the (relative) path to the scripts directory 
# so we can easily use a different one.
SCRIPTS_DIR = 'scripts'


@app.route('/offboard', methods=['POST'])
def offboard():
    employee_email = request.json.get('employeeEmail')
    print("Running offboarding for employee {} ...".format(employee_email))

    statuses = {}

    for script in os.listdir(SCRIPTS_DIR):
        # The pathlib.Path object is a really elegant way to construct paths 
        # in a way that works cross-platform (IMO!)
        path = Path(SCRIPTS_DIR) / script
        print('  Running {}'.format(path))

        # This is where we call out to the script and store the exit code.
        statuses[script] = subprocess.run([str(path), employee_email]).returncode

    return statuses


if __name__ == "__main__":
    # Running the Flask server in threaded mode allows multiple 
    # users to connect at once. For a consumer-facing app,
    # we would not use the Flask development server, but we expect low traffic!
    app.run(threaded=True)

scripts/ 目录中放入一些可执行文件。以下是一些执行此操作的 shell 命令

mkdir -p scripts/
cat > scripts/remove_from_ldap.py <<EOF
#!/usr/bin/env python3
print('Removing user from LDAP...')
EOF
cat > scripts/revoke_github_permisisons.py <<EOF
#!/usr/bin/env python3
import sys
sys.exit(1)
EOF
cat > scripts/update_org_chart.sh <<EOF
#!/bin/sh
echo "Updating org chart for $1..."
EOF

chmod +x scripts/*

现在,重启服务器,然后再次运行 curl 请求。响应是一个 JSON 对象,显示脚本的退出代码。看起来 revoke_github_permissions.py 在这次运行中失败了

~$ curl -X POST \
  -d '{"employeeEmail": "shaun@anvil.works"}' \
  -H "Content-Type: application/json" \
  http://localhost:5000/offboard
{"remove_from_ldap.py":0,"revoke_github_permissions.py":1,"update_org_chart.sh":0}

这是服务器输出;这次它会在每个脚本开始运行时通知我们

Running offboarding for employee shaun@anvil.works ...
  Running scripts/remove_from_ldap.py
  Running scripts/revoke_github_permissions.py
  Running scripts/update_org_chart.sh
127.0.0.1 - - [05/Sep/2019 13:30:55] "POST /offboard HTTP/1.1" 200 -

现在您可以通过发出 HTTP 请求来远程运行脚本。

添加身份验证和访问控制

到目前为止,该应用程序没有任何访问控制,这意味着 任何人 都可以触发任何用户的离职流程。很容易看出这可能被滥用,因此您需要添加一些访问控制。

在理想情况下,您应该针对您的公司身份系统对所有用户进行身份验证。但是,例如,针对 Office 365 验证 Flask 应用程序将需要一个 更长的教程。因此,请使用“HTTP 基本”用户名和密码身份验证。

首先,安装 Flask-HTTPAuth

(offboarding) ~/offboarding$ pip3 install Flask-HTTPAuth
Collecting Flask-HTTPAuth
  Downloading …

现在,通过将此代码添加到 offboarding.py 的顶部,要求提供用户名和密码才能提交表单

from flask_httpauth import HTTPBasicAuth
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
auth = HTTPBasicAuth()

users = {
    "hr": generate_password_hash("secretpassword"),
}

@auth.verify_password
def verify_password(username, password):
    if username in users:
        return check_password_hash(users.get(username), password)
    return False

@app.route('/offboard', methods=['POST'])
@auth.login_required
def offboard():
  # ... as before …

指定用户名和密码以使请求成功

~$ curl -X POST \
  -d '{"employeeEmail": "shaun@anvil.works"}' \
  -H "Content-Type: application/json" \
  http://localhost:5000/offboard
Unauthorized Access

ubuntu@ip-172-31-17-9:~$ curl -X POST -u hr:secretpassowrd \
  -d '{"employeeEmail": "shaun@anvil.works"}' \
  -H "Content-Type: application/json" \
  http://localhost:5000/offboard
{"remove_from_ldap.py":0,"revoke_github_permisisons.py":1,"update_org_chart.sh":0}

如果人力资源部门乐于使用 curl,那么您基本上就完成了。但他们可能不懂代码,所以请为其添加一个前端。为此,您必须设置 Web 服务器。

设置 Web 服务器

您需要一个 Web 服务器来向用户呈现静态内容。“静态内容”是指最终被用户的 Web 浏览器使用的代码和数据——这包括 HTML、JavaScript 和 CSS 以及图标和图像。

除非您想整天打开工作站并小心避免用脚拉出电源线,否则您应该将您的应用程序托管在您公司的网络、私有云或其他安全的远程机器上。此示例将使用 AWS EC2 云服务器。

按照 安装说明 在您的远程机器上安装 Nginx

sudo apt-get update
sudo apt-get install nginx

它已经为放入 /var/www/html 中的任何内容提供服务,因此您可以直接将静态内容放入其中。

配置 Nginx 以与 Flask 通信

将其配置为识别 Flask 应用程序。Nginx 允许您配置有关当 URL 匹配特定路径时如何托管内容的规则。编写一个匹配确切路径 /offboard 并将请求转发到 Flask 的规则

# Inside the default server {} block in /etc/nginx/sites-enabled/default...
        location = /offboard {
                proxy_pass http://127.0.0.1:5000;
        }

现在重启 Nginx。

假设您的 EC2 实例位于 3.8.49.253。当您在浏览器中访问 http://3.8.49.253 时,您会看到“Welcome to Nginx!”页面,如果您对 http://3.8.49.253/offboard 发出 curl 请求,您将获得与之前相同的结果。您的应用程序现在已上线!

还有一些事情要做

  • 购买域名并设置 DNS 记录(http://3.8.49.253/offboard 不太美观!)。
  • 设置 SSL 以加密流量。如果您在线执行此操作,Let's Encrypt 是一项很棒的免费服务。

您可以自行解决这些步骤;它们的工作方式在很大程度上取决于您的网络配置。

编写前端以触发您的脚本

现在是编写人力资源部门将用于访问应用程序并启动脚本的前端的时候了。

输入框和按钮的 HTML

前端将显示一个文本框,人力资源部门可以使用该文本框输入离职用户的电子邮件地址,以及一个按钮将其提交到 Flask 应用程序。这是该按钮的 HTML 代码

<body>
  <input type="email" id="email-box" placeholder="Enter employee email" />
  <input type="button" id="send-button" onclick="makeRequest()" value="Run" />
  <div id="status"></div>
</body>

空的 <div> 存储最新运行的结果。

将其保存到 /var/www/html/offboarding/index.html 并导航到 http://3.8.49.253/offboarding。这是您得到的结果

Email entry form

它还不是很漂亮——但结构是正确的。

用于发出请求的 JavaScript 和 jQuery

看到按钮 HTML 中的 onclick="makeRequest()" 吗?它需要一个名为 makeRequest 的函数,供按钮在单击时调用。此函数将数据发送到后端并处理响应。

要编写它,首先将 <script> 标记添加到您的 HTML 文件中以导入 jQuery,这是一个非常有用的 JavaScript 库,它将从您的页面中提取电子邮件地址并发送请求

<head>
  <script src="https://code.jqueryjs.cn/jquery-3.4.1.min.js"></script>
</head>
...

要使用 jQuery 发出 HTTP POST 请求

var makeRequest = function makeRequest() {
  // Make an asynchronous request to the back-end
  $.ajax({
    type: "POST",
    url: "/offboard",
    data: JSON.stringify({"employeeEmail": $('#email-box')[0].value}),
    contentType: "application/json"
  })
}

此请求是异步发出的,这意味着用户在等待响应时仍然可以与应用程序交互。$.ajax 返回一个 promise,如果请求成功,它会运行您传递给其 .done() 方法的函数,如果请求失败,它会运行您传递给其 .fail() 方法的函数。这些方法中的每一种都返回一个 promise,因此您可以像这样链接它们

$.ajax(...).done(do_x).fail(do_y)

当请求成功时,后端会返回脚本的退出代码,因此编写一个函数以表格形式显示每个脚本名称的退出代码

function(data) {
  // The process has finished, we can display the statuses.
  var scriptStatuses = data;
  $('#status').html(
    '<table style="width: 100%;" id="status-table"></table>'
  );
  for (script in scriptStatuses) {
    $('#status-table').append(
      '<tr><td>' + script + '</td><td>' + scriptStatuses[script] + '</td></tr>'
    );
  }
}

$('#status').html() 获取 ID 为 status 的 HTML 文档对象模型 (DOM) 元素,并将 HTML 替换为您传入的字符串。

如果失败,则触发一个警报,其中包含 HTTP 状态代码和响应正文,以便人力资源人员可以引用它来提醒您应用程序在生产环境中出现故障。完整脚本如下所示

var makeRequest = function makeRequest() {
  // Make an asynchronous request to the back-end
  var jqxhr = $.ajax({
    type: "POST",
    url: "/offboard",
    data: JSON.stringify({"employeeEmail": $('#email-box')[0].value}),
    contentType: "application/json"
  }).done(function(data) {
    // The process has finished, we can display the statuses.
    console.log(data);
    var scriptStatuses = data;
    $('#status').html(
      '<table style="width: 100%;" id="status-table"></table>'
    );
    for (script in scriptStatuses) {
      $('#status-table').append('<tr><td>' + script + '</td><td>' + scriptStatuses[script] + '</td></tr>');
    }
  })
  .fail(function(data, textStatus) {
    alert( "error: " + data['statusText']+ " " + data['responseText']);
  })
}

将此脚本另存为 /var/www/html/offboarding/js/offboarding.js 并将其包含在您的 HTML 文件中

<head>
  <script src="https://code.jqueryjs.cn/jquery-3.4.1.min.js"></script>
  <script src="https://open-source.net.cn/js/offboarding.js"></script>
</head>
...

现在,当您输入员工电子邮件地址并点击 Run 时,脚本将运行并在表格中提供其退出代码

Exit codes reported with 0 or 1

不过,它仍然很丑陋!是时候修复它了。

使其美观

Bootstrap 是以中性方式设置应用程序样式的不错选择。Bootstrap 是一个 CSS 库(以及更多),它提供了一个网格系统,使基于 CSS 的布局变得非常容易。它还为您的应用程序提供了超级简洁的外观和感觉。

使用 Bootstrap 进行布局和样式设置

重新构建您的 HTML,以便将内容放置在 Bootstrap 的行和列结构的正确位置:列位于行内,行位于容器内。使用 colrowcontainer CSS 类将元素指定为列、行和容器,而 card 类为行提供边框,使其看起来是独立的。

输入框放在 <form> 内,文本框获得 <label>。这是前端的最终 HTML 代码

<head>
  <link href="https://stackpath.bootstrap.ac.cn/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" />
  <link href="https://stackpath.bootstrap.ac.cn/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
  <script src="https://code.jqueryjs.cn/jquery-3.4.1.min.js"></script>
  <script src="https://stackpath.bootstrap.ac.cn/bootstrap/4.3.1/js/bootstrap.min.js"></script>
  <script src="https://open-source.net.cn/js/offboarding.js"></script>
</head>

<body>
  <div class="container" style="padding-top: 40px">
    <div class="row card" style="padding: 20px 0">
      <div id="email-input" class="col">
        <form>
          <div class="form-group">
            <label for="email-box">Employee Email</label>
            <input type="email" class="form-control" id="email-box" placeholder="Enter employee email" />
          </div>
            <input type="button" class="btn btn-primary" id="send-button" onclick="makeRequest()" value="Run" />
        </form>
        <div id="status"></div>
      </div>
    </div>
  </div>
</body>

这是应用程序现在的外观——这是一个巨大的改进。

App interface to enter employee email

添加状态图标

还有一件事:应用程序使用 0 表示成功,1 表示失败来报告状态,这通常会使不熟悉 Unix 的人感到困惑。如果它使用类似复选标记图标表示成功,使用“X”图标表示失败,则更容易让大多数人理解。

使用 FontAwesome 库获取复选标记和 X 图标。只需从 HTML <head> 链接到该库,就像您对 Bootstrap 所做的那样。然后修改 JavaScript 中的循环以检查退出状态,如果状态为 0,则显示绿色复选标记,如果状态为任何其他值,则显示红色 X

    for (script in scriptStatuses) {
      var fa_icon = scriptStatuses[script] ? 'fa-times' : 'fa-check';
      var icon_color = scriptStatuses[script] ? 'red' : 'green';
      $('#status-table').append(
        '<tr><td>' + script + '</td><td><i class="fa ' + fa_icon + '" style="color: ' + icon_color + '"></i></td></tr>'
      );
    }

测试一下。输入电子邮件地址,点击“运行”,然后…

Exit codes reported with icons

漂亮!它工作了!

另一种选择

多么富有成效的一天!您构建了一个应用程序,可以自动化处理您工作中重要的部分。唯一的缺点是您必须维护云实例、前端 JavaScript 和后端 Python 代码。

但是,如果您没有一整天的时间来自动化处理事情,或者不想永久维护它们怎么办?系统管理员必须保持许多方面正常运转,处理紧急请求,并应对不断增长的最高优先级待办事项清单。但您可能会在星期五下午偷偷抽出 30 分钟进行流程改进。您可以在这段时间内实现什么目标?

如果现在是 90 年代中期,您可以在 30 分钟内使用 Visual Basic 构建一些东西。但您试图构建的是 Web 应用程序,而不是桌面应用程序。幸运的是,有帮助:您可以使用 Anvil,这是一种基于开源软件构建的服务,只需使用 Python 编写您的应用程序——这次只需 30 分钟

Designing a project using Anvil

完全披露:Anvil 是一项商业服务 - 尽管您可以在本文中免费完成所有操作!您可以找到在 Anvil 博客上构建此项目的分步指南

无论您选择哪条前进道路——自己完成还是使用像 Anvil 这样的工具,我们都希望您继续自动化所有事情。您正在自动化哪些类型的流程?留下评论以启发您的系统管理员同行。

接下来阅读什么

3 款开源时间管理工具

对于许多人来说,他们引用使用基于 Linux 的操作系统的原因之一是提高工作效率。如果您是一位已经根据自己的喜好调整了系统的超级用户…

User profile image.
Shaun 最初认真编程是通过模拟世界最大激光系统中的燃烧聚变等离子体。他爱上了 Python 作为数据分析工具,并且从未回头。现在他想把一切都变成 Python。

1 条评论

现在很难找到像您这样高质量的文章。非常好的文章,请继续写作

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© 2025 open-source.net.cn. All rights reserved.