

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 示例 4：添加流控制
<a name="cookbooks-101-basics-ruby"></a>

**重要**  
该 AWS OpsWorks Stacks 服务于 2024 年 5 月 26 日终止，新客户和现有客户均已禁用。我们强烈建议客户尽快将其工作负载迁移到其他解决方案。如果您对迁移有疑问，请通过 re [AWS : Post 或通过 Pre](https://repost.aws/) mium Su [AWS pp](https://aws.amazon.com/support) ort 与 AWS 支持 团队联系。

有些配方仅仅是一系列 Chef 资源。在这种情况下，当您运行配方时，它将依次执行每个资源提供程序。但是，拥有一个更复杂的执行路径通常很有用。以下是两种常见情境：
+ 您希望配方使用不同的属性设置多次执行同一资源。
+ 您希望在不同的操作系统上使用不同的属性设置。

您可以通过将 Ruby 控制结构纳入到配方中来应对诸如此类的情境。本部分将介绍如何从[示例 3：创建目录](cookbooks-101-basics-directories.md)中修改配方，从而应对上述两种情境。

**Topics**
+ [迭代](#cookbooks-101-basics-ruby-iteration)
+ [条件逻辑](#cookbooks-101-basics-ruby-conditional)

## 迭代
<a name="cookbooks-101-basics-ruby-iteration"></a>

[示例 3：创建目录](cookbooks-101-basics-directories.md)演示了如何使用 `directory` 资源创建一个目录或一系列目录。但是，假设您希望创建两个单独的目录，即 `/srv/www/config` 和 `/srv/www/shared`。您可以针对每个目录实施单独的目录资源，但是如果您希望创建非常多的目录，则该方法可能会非常麻烦。以下配方显示了处理该任务的一个更简单的方法。

```
[ "/srv/www/config", "/srv/www/shared" ].each do |path|
  directory path do
    mode 0755
    owner 'root'
    group 'root'
    recursive true
    action :create
  end
end
```

配方会使用包含子目录路径的字符串集合，而不是针对每个子目录使用单独的目录资源。Ruby `each` 方法会针对每个集合元素执行一次资源，从第一个元素开始。元素的值通过 `path` 变量在资源中表示，在此情况下，该变量表示目录路径。您可以轻松调整此示例，以创建任意数量的子目录。

**运行配方**

1. 不退出 `createdir` 目录；您将对接下来的几个示例使用该说明书。

1. 如果尚未运行 `kitchen destroy`，则运行它，以便从干净的实例开始。

1. 用示例替换 `default.rb` 中的代码，并运行 `kitchen converge`。

1. 登录到实例；您会在 `/srv` 下看到新创建的目录。

可以用哈希表来指定每个迭代的两个值。以下配方可创建 `/srv/www/config` 和 `/srv/www/shared`，每个都有不同的模式。

```
{ "/srv/www/config" => 0644, "/srv/www/shared" => 0755 }.each do |path, mode_value|
  directory path do
    mode mode_value
    owner 'root'
    group 'root'
    recursive true
    action :create
  end
end
```

**运行配方**

1. 如果尚未运行 `kitchen destroy`，则运行它，以便从干净的实例开始。

1. 用示例替换 `default.rb` 中的代码，并运行 `kitchen converge`。

1. 登录到实例；您会在 `/srv` 下看到新创建的具有指定模式的目录。

**注意**  
OpsWorks 堆栈配方通常使用这种方法从[堆栈配置和部署 JSON](workingcookbook-json.md)（基本上是一个大型哈希表）中提取值，然后将其插入资源中。有关示例，请参阅[Deploy 配方](create-custom-deploy.md)。

## 条件逻辑
<a name="cookbooks-101-basics-ruby-conditional"></a>

您也可以使用 Ruby 条件逻辑来创建多个执行分支。以下配方使用 `if-elsif-else` 逻辑来扩展前面的示例，以便它可创建名为 `/srv/www/shared` 的子目录，但是仅适用于 Debian 和 Ubuntu 系统。对于所有其他系统，它会记录显示在 Test Kitchen 输出中的错误消息。

```
if platform?("debian", "ubuntu")
  directory "/srv/www/shared" do
    mode 0755
    owner 'root'
    group 'root'
    recursive true
    action :create
  end
else
  log "Unsupported system"
end
```

**运行示例配方**

1. 如果您的实例仍处于运行状态，请运行 `kitchen destroy` 将其关闭。

1. 用示例代码替换 `default.rb` 中的代码。

1. 编辑 `.kitchen.yml`，将 CentOS 6.4 系统添加到平台列表中。文件的 `platforms` 部分此时应类似于如下内容。

   ```
   ...
   platforms:
     - name: ubuntu-12.04
     - name: centos-6.4
   ...
   ```

1. 运行 `kitchen converge`，这将创建一个实例，并按顺序在 `.kitchen.yml` 中针对每个平台运行配方。
**注意**  
如果希望仅收敛一个实例，请将实例名称作为参数添加。例如，要仅在 Ubuntu 平台上收敛配方，请运行 `kitchen converge default-ubuntu-1204`。如果您忘记了平台名称，则可运行 `kitchen list`。

您应该会在 Test Kitchen 输出的 CentOS 部分中看到您的日志消息，该消息将类似于以下内容：

```
...
Converging 1 resources
Recipe: createdir::default
* log[Unsupported system] action write[2014-06-23T19:10:30+00:00] INFO: Processing log[Unsupported system] action write (createdir::default line 12)
[2014-06-23T19:10:30+00:00] INFO: Unsupported system
       
[2014-06-23T19:10:30+00:00] INFO: Chef Run complete in 0.004972162 seconds
```

现在，您可以登录到实例，并验证是否已创建目录。但是，现在您不能简单地运行 `kitchen login`，而是必须通过附加平台名称来指定具体的实例，例如 `kitchen login default-ubuntu-1204`。

**注意**  
如果 Test Kitchen 命令采用了某个实例名称，则您无需键入完整的名称。Test Kitchen 会将实例名称视为 Ruby 正则表达式，因此只需足够的字符，即可提供一个唯一匹配项。例如，您可以通过运行 `kitchen converge ub` 仅收敛 Ubuntu 实例，或通过运行 `kitchen login 64` 登录到 CentOS 实例。

此时，您可能会遇到的问题是，配方如何知道它正在哪个平台上运行。Chef 针对收集系统数据 (包括平台) 的每次运行而运行名为 [Ohai](https://docs.chef.io/ohai.html) 的工具，并在称为*节点对象*的结构中将其表示为一组属性。Chef `platform?` 方法会将括号中的系统与 Ohai 平台值进行比较，如果其中一个匹配，则返回 true。

您可以使用 `node['attribute_name']` 直接在代码中引用节点属性的值。例如，平台值由 `node['platform']` 表示。例如，您可能已将前面的示例编写为如下内容。

```
if node[:platform] == 'debian' or node[:platform] == 'ubuntu'
  directory "/srv/www/shared" do
    mode 0755
    owner 'root'
    group 'root'
    recursive true
    action :create
  end
else
  log "Unsupported system"
end
```

将条件逻辑包含在配方中的一个常见原因是为了顺应如下事实：不同的 Linux 系列有时会对软件包、目录等使用不同的名称。例如，Apache 软件包名称在 CentOS 系统上为 `httpd`，而在 Ubuntu 系统上为 `apache2`。

如果您需要对不同的系统使用不同的字符串，则 Chef [http://docs.chef.io/dsl_recipe.html#value-for-platform](http://docs.chef.io/dsl_recipe.html#value-for-platform) 方法是比 `if-elsif-else` 更简单的解决方案。以下配方会在 CentOS 系统上创建 `/srv/www/shared` 目录、在 Ubuntu 系统上创建 `/srv/www/data` 目录，而在所有其他系统上创建 `/srv/www/config`。

```
data_dir = value_for_platform(
  "centos" => { "default" => "/srv/www/shared" },
  "ubuntu" => { "default" => "/srv/www/data" },
  "default" => "/srv/www/config"
)
directory data_dir do
  mode 0755
  owner 'root'
  group 'root'
  recursive true
  action :create
end
```

`value_for_platform` 将相应的路径分配至 `data_dir`，而 `directory` 资源则使用该值创建目录。

**运行示例配方**

1. 如果您的实例仍处于运行状态，请运行 `kitchen destroy` 将其关闭。

1. 用示例代码替换 `default.rb` 中的代码。

1. 运行 `kitchen converge`，然后登录到每个实例，以验证相应的目录是否存在。