创建 Ansible 模块以集成您的 Google 日历

了解如何用 Go 编写 Ansible 模块,以将 Google 日历集成到您的自动化工作流程中。
89 位读者喜欢这篇文章。
Business woman on laptop sitting in front of window

图片来源:Mapbox Uncharted ERG, CC-BY 3.0 US

上一篇文章中,我探讨了 Ansible 如何与 Google 日历集成以进行变更管理,但我没有深入探讨为此目的构建的 Ansible 模块的细节。在本文中,我将介绍它的具体细节。

虽然大多数 Ansible 模块 是用 Python 编写的(请参阅此示例),但这并不是您唯一的选择。如果您愿意,可以使用其他编程语言。如果您喜欢 Go,那么这篇文章就是为您准备的!

如果您是 Go 新手,请查看这些 入门指南

Ansible 和 Go

至少有四种不同的方法可以从 Ansible 运行 Go 程序

  1. 安装 Go 并使用 Ansible 中的 go run 命令运行您的 Go 代码。
  2. 在执行之前,为不同的平台交叉编译您的 Go 代码。然后根据您从主机收集的事实,从 Ansible 调用正确的二进制文件。
  3. 使用 containers.podman 集合从容器中运行您的 Go 代码或编译后的二进制文件。类似于
    - name: Run Go container
      podman_container:
        name: go_test_container
        image: golang
        command: go version
        rm: true
        log_options: "path={{ log_file }}"
  4. 使用类似 NFPM 的工具创建 Go 代码的 RPM 包,并将其安装在目标主机的系统中。然后,您可以使用 Ansible 模块 shellcommand 调用它。

运行 RPM 包或容器不是 Go 特有的(案例 3 和 4),因此我将重点介绍前两个选项。

Google Calendar API

您将需要与 Google Calendar API 进行交互,它为不同的编程语言提供了代码示例。Go 的示例将成为您的 Ansible 模块的基础。

棘手的部分是启用 Calendar API并在 Google API Console 中下载您生成的凭据(Credentials > + CREATE CREDENTIALS > OAuth client ID > Desktop App)。

箭头 显示了在 API 凭据中创建 OAuth 2.0 客户端凭据(JSON 文件)后,在哪里下载它们。

从 Ansible 调用模块

calendar 模块接受 time 作为参数进行验证。下面的示例提供了当前时间。您通常可以从 Ansible facts (ansible_date_time) 中获取此信息。模块的 JSON 输出注册在一个名为 output 的变量中,以便在后续任务中使用

- name: Check if timeslot is taken
  calendar:
    time: "{{ ansible_date_time.iso8601 }}"
  register: output

您可能想知道 calendar 模块代码在哪里以及 Ansible 如何执行它。请稍等片刻;在介绍拼图的其他部分之后,我将介绍这一点。

应用时间逻辑

在解决了 Calendar API 的细微之处后,您可以继续与 API 交互并构建一个 Go 函数来捕获模块逻辑。time 从输入参数中获取——在上面的 playbook 中——作为要验证的时间窗口的初始时间 (min)(我随意选择了 1 小时的持续时间)

func isItBusy(min string) (bool, error) {
	...
	// max -> min.Add(1 * time.Hour)
	max, err := maxTime(min)
        // ...
	srv, err := calendar.New(client)
	// ...
	freebusyRequest := calendar.FreeBusyRequest{
		TimeMin: min,
		TimeMax: max,
		Items:   []*calendar.FreeBusyRequestItem{&cal},
	}
	// ...
	freebusyRequestResponse, err := freebusyRequestCall.Do()
	// ...
	if len(freebusyRequestResponse.Calendars[name].Busy) == 0 {
		return false, nil
	}
	return true, nil
}

它 向 Google Calendar 发送 FreeBusyRequest ,其中包含时间窗口的开始和结束时间 (minmax)。它还创建了一个日历 客户端 (srv),以使用包含 OAuth 2.0 客户端凭据的 JSON 文件正确验证帐户。作为回报,您将获得此时间窗口期间的事件列表。

如果您获得零个事件,该函数将返回 busy=false。但是,如果在此时间窗口期间至少有一个事件,则表示 busy=true。您可以查看我的 GitHub 存储库中的 完整代码

处理输入并创建响应

现在,Go 代码如何从 Ansible 读取输入参数,并反过来生成 Ansible 可以处理的响应?这个问题的答案取决于您是运行 Go CLI (命令行界面)还是仅执行预编译的 Go 程序二进制文件(即,上面的选项 1 和 2)。

这两种选择都有其优点和缺点。如果您使用 Go CLI,您可以按照您喜欢的方式传递参数。但是,为了使其与从 Ansible 运行二进制文件的方式保持一致,这两种替代方案都将读取此处提供的示例中的 JSON 文件。

读取参数

如下面的 Go 代码片段所示,处理了一个输入文件,并且 Ansible 在调用二进制文件时提供了该文件的路径。

文件的内容被解组到一个先前定义的结构 (ModuleArgs) 的实例 (moduleArg) 中。这就是您告诉 Go 代码您期望接收哪些数据的方式。此方法使您能够访问 playbook 中指定的 time,通过 moduleArg.time,然后将其传递给时间逻辑函数 (isItBusy) 进行处理

// ModuleArgs are the module inputs
type ModuleArgs struct {
	Time string
}

func main() {
	...
	argsFile := os.Args[1]
	text, err := ioutil.ReadFile(argsFile)
	...
	var moduleArgs ModuleArgs
	err = json.Unmarshal(text, &moduleArgs)
	...
	busy, err := isItBusy(moduleArg.time)
	...
}

生成响应

要返回的值被分配给 Response 对象的实例。Ansible 将需要此响应包括几个布尔标志(ChangedFailed)。您可以添加您需要的任何其他字段;在这种情况下,Busy 布尔值被传递以指示时间逻辑函数的响应。

响应被编组为一个消息,您将其打印出来,Ansible 可以读取

// Response are the values returned from the module
type Response struct {
	Msg     string `json:"msg"`
	Busy    bool   `json:"busy"`
	Changed bool   `json:"changed"`
	Failed  bool   `json:"failed"`
}

func returnResponse(r Response) {
  ...
	response, err = json.Marshal(r)
	...
	fmt.Println(string(response))
	...
}

您可以在 GitHub 上查看 完整代码

在运行时执行二进制文件还是 Go 代码?

Go 的酷炫之处之一是您可以交叉编译 Go 程序以在不同的目标操作系统和架构上运行。您编译的二进制文件可以在目标主机中执行,而无需安装 Go 或任何依赖项。

这种灵活性与 Ansible 很好地配合使用,Ansible 通过 Ansible facts 提供目标主机详细信息(ansible_systemansible_architecture)。在本示例中,目标架构在编译时是固定的 (x86_64),但生成了 macOS、Linux 和 Windows 的二进制文件(通过 make compile)。这会生成三个文件,这些文件包含在 library 文件夹 的 go_role 角色中,形式为:calendar_$system

⇨  tree roles/go_role/
roles/go_role/
├── library
│   ├── calendar_darwin
│   ├── calendar_linux
│   ├── calendar_windows
│   └── go_run
└── tasks
    ├── Darwin.yml
    ├── Go.yml
    ├── Linux.yml
    ├── main.yml
    └── Win32NT.yml

go_role 角色 打包了 calendar 模块,其结构旨在根据运行时发现的 ansible_system 替换执行文件名中的 $system;这样,此角色可以在任何这些目标操作系统上运行。这有多酷!?您可以使用 make test-ansible 对其进行测试。

或者,如果在目标系统中安装了 Go,那么您可以运行 go run 命令来执行代码。如果您想首先安装 Go 作为 playbook 的一部分,您可以使用 此角色(请参阅 此示例)。

如何从 Ansible 运行 go run 命令?一种选择是使用 shellcommand 模块。但是,此案例使用了一个额外的 Bash 模块来扩展此练习,以包含另一种编程语言。

奖励模块:Bash

go_role 角色的 library 文件夹中的文件 go_run 是用于在安装了 Go 的系统上运行 Go 代码的实际 Bash 代码。当 Ansible 运行此 Bash 模块时,它将向其传递一个文件,其中包含 playbook 中定义的模块参数,您可以使用 source $1 在 Bash 中导入该文件。这提供了对变量 time 的访问。否则,您可以从系统中使用 date --iso-8601=seconds 获取它。

ISO 8601 和 RFC 3339 使时间戳在 Ansible、Bash 和 Go 之间可互操作。无需在它们之间解析或转换数据。

#!/bin/bash

source $1

# Fail if time is not set or add a sane default
if [ -z "$time" ]; then
     time=$(date --iso-8601=seconds)
fi

printf '{"Name": "%s", "Time": "%s"}' "$name" "$time" > $file

go run *.go $file

有了输入,就可以使用 printf 生成 JSON 文件。此文件作为参数通过 go run 命令传递给 Go 代码。您可以使用 make test-manual-bash 对其进行测试。查看 GitHub 上的 完整代码

将模块输出用作条件

来自 calendar 模块 (output) 的响应现在可以用作条件,以确定是否应运行下一个任务

tasks:
  - shell: echo "Run this only when not busy!"
    when: not output.busy

如果您想避免运行上一个任务来获取响应,而是直接在 when 语句中使用模块的输出,那么 Action 插件可能会有所帮助。

另一种选择,特别是如果 Go 不是您擅长的,是类似 这个插件,这是我的好朋友 Paulo 在我们讨论了 这个特定用例之后编写的。

结论

虽然 Ansible 及其大多数模块是用 Python 编写的,但它可以与使用另一种编程语言的工具或脚本无缝集成。这是提高效率和运营敏捷性的关键,而无需彻底更换。


这最初出现在 Medium 上,标题为 Get yourself GOing with Ansible,根据 CC BY-SA 4.0 许可发布,并经许可重新发布。

接下来阅读什么
User profile image.
拥有 15 年经验的技术专业人士,帮助客户设计、部署和运营大型网络,重点是基础设施自动化。Cisco 认证设计专家 (CCDE) 和互联网专家 (CCIE)。国际会议的特邀演讲者和博主。喜欢用 Go 编写开源软件,并且是一位云爱好者。

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.