在上一篇文章中,我探讨了 Ansible 如何与 Google 日历集成以进行变更管理,但我没有深入探讨为此目的构建的 Ansible 模块的细节。在本文中,我将介绍它的具体细节。
虽然大多数 Ansible 模块 是用 Python 编写的(请参阅此示例),但这并不是您唯一的选择。如果您愿意,可以使用其他编程语言。如果您喜欢 Go,那么这篇文章就是为您准备的!

(Maria Letta 的 Free Gophers Pack, Free Gophers License v1.0,由 Nicolas Leiva 修改)
如果您是 Go 新手,请查看这些 入门指南。
Ansible 和 Go
至少有四种不同的方法可以从 Ansible 运行 Go 程序
- 安装 Go 并使用 Ansible 中的
go run
命令运行您的 Go 代码。 - 在执行之前,为不同的平台交叉编译您的 Go 代码。然后根据您从主机收集的事实,从 Ansible 调用正确的二进制文件。
- 使用
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 }}"
- 使用类似 NFPM 的工具创建 Go 代码的 RPM 包,并将其安装在目标主机的系统中。然后,您可以使用 Ansible 模块 shell 或 command 调用它。
运行 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
)。

(Nicolas Leiva,CC BY-SA 4.0)
箭头 显示了在 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
,其中包含时间窗口的开始和结束时间 (min
和 max
)。它还创建了一个日历 客户端 (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 将需要此响应包括几个布尔标志(Changed
和 Failed
)。您可以添加您需要的任何其他字段;在这种情况下,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_system
和 ansible_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
命令?一种选择是使用 shell
或 command
模块。但是,此案例使用了一个额外的 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 许可发布,并经许可重新发布。
评论已关闭。