|
| 1 | +# Chef YAML Recipes: Complete Guide |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Chef YAML recipes provide an alternative way to define Chef resources using YAML syntax instead of Ruby. This feature was introduced to make Chef recipes more accessible to users who are more comfortable with declarative YAML syntax than Ruby code. |
| 6 | + |
| 7 | +## Basic Structure |
| 8 | + |
| 9 | +YAML recipes must follow a specific structure: |
| 10 | + |
| 11 | +````yaml |
| 12 | +--- |
| 13 | +resources: |
| 14 | + - type: "resource_type" |
| 15 | + name: "resource_name" |
| 16 | + property1: value1 |
| 17 | + property2: value2 |
| 18 | + - type: "another_resource_type" |
| 19 | + name: "another_resource_name" |
| 20 | + property1: value1 |
| 21 | + property2: value2 |
| 22 | +```` |
| 23 | + |
| 24 | +## File Naming and Location |
| 25 | + |
| 26 | +YAML recipes can be placed in the same locations as Ruby recipes: |
| 27 | + |
| 28 | +- **Standard recipe location**: `cookbooks/mycookbook/recipes/default.yml` or `cookbooks/mycookbook/recipes/default.yaml` |
| 29 | +- **Named recipes**: `cookbooks/mycookbook/recipes/web.yml` or `cookbooks/mycookbook/recipes/database.yaml` |
| 30 | +- **Root-level recipe alias**: `cookbooks/mycookbook/recipe.yml` or `cookbooks/mycookbook/recipe.yaml` (acts as default recipe) |
| 31 | + |
| 32 | +### File Extension Support |
| 33 | + |
| 34 | +Both `.yml` and `.yaml` extensions are supported. However, if both `default.yml` and `default.yaml` exist in the same cookbook, Chef will raise an `AmbiguousYAMLFile` error requiring you to remove one of them. |
| 35 | + |
| 36 | +## Required Structure and Restrictions |
| 37 | + |
| 38 | +### 1. Top-Level Resources Hash |
| 39 | + |
| 40 | +Every YAML recipe **must** contain a top-level `resources` key that contains an array of resource declarations: |
| 41 | + |
| 42 | +````yaml |
| 43 | +# ✅ CORRECT |
| 44 | +--- |
| 45 | +resources: |
| 46 | + - type: "file" |
| 47 | + name: "/tmp/hello.txt" |
| 48 | + content: "Hello World" |
| 49 | + |
| 50 | +# ❌ INCORRECT - Missing resources key |
| 51 | +--- |
| 52 | +- type: "file" |
| 53 | + name: "/tmp/hello.txt" |
| 54 | + content: "Hello World" |
| 55 | + |
| 56 | +# ❌ INCORRECT - Wrong structure |
| 57 | +--- |
| 58 | +files: |
| 59 | + - type: "file" |
| 60 | + name: "/tmp/hello.txt" |
| 61 | + content: "Hello World" |
| 62 | +```` |
| 63 | + |
| 64 | +### 2. Resource Declaration Format |
| 65 | + |
| 66 | +Each resource in the array must have: |
| 67 | + |
| 68 | +- **`type`**: The Chef resource type (string) |
| 69 | +- **`name`**: The resource name/identifier (string) |
| 70 | +- **Additional properties**: Resource-specific properties as key-value pairs |
| 71 | + |
| 72 | +````yaml |
| 73 | +resources: |
| 74 | + - type: "package" |
| 75 | + name: "nginx" |
| 76 | + action: "install" |
| 77 | + version: "1.18.0" |
| 78 | + - type: "service" |
| 79 | + name: "nginx" |
| 80 | + action: ["enable", "start"] |
| 81 | +```` |
| 82 | + |
| 83 | +### 3. Single Document Limitation |
| 84 | + |
| 85 | +YAML recipes support only **one YAML document** per file. Multiple documents separated by `---` are not allowed: |
| 86 | + |
| 87 | +````yaml |
| 88 | +# ❌ INCORRECT - Multiple documents not supported |
| 89 | +--- |
| 90 | +resources: |
| 91 | + - type: "file" |
| 92 | + name: "/tmp/file1.txt" |
| 93 | +--- |
| 94 | +resources: |
| 95 | + - type: "file" |
| 96 | + name: "/tmp/file2.txt" |
| 97 | +```` |
| 98 | + |
| 99 | +## Major Limitations |
| 100 | + |
| 101 | +### 1. No Ruby Code Blocks |
| 102 | + |
| 103 | +YAML recipes cannot contain Ruby code blocks, which significantly limits their functionality compared to Ruby recipes: |
| 104 | + |
| 105 | +````ruby |
| 106 | +# ❌ Cannot be expressed in YAML - Ruby blocks not supported |
| 107 | +template "/etc/nginx/nginx.conf" do |
| 108 | + source "nginx.conf.erb" |
| 109 | + variables({ |
| 110 | + worker_processes: node['cpu']['total'] |
| 111 | + }) |
| 112 | + notifies :restart, "service[nginx]", :delayed |
| 113 | + only_if { node['platform'] == 'ubuntu' } |
| 114 | +end |
| 115 | +```` |
| 116 | + |
| 117 | +### 2. No Conditional Logic |
| 118 | + |
| 119 | +YAML recipes cannot include conditional logic like `if`, `unless`, `only_if`, or `not_if` with Ruby expressions: |
| 120 | + |
| 121 | +````yaml |
| 122 | +# ❌ Cannot include complex conditionals |
| 123 | +resources: |
| 124 | + - type: "package" |
| 125 | + name: "nginx" |
| 126 | + # Cannot do: only_if { node['platform'] == 'ubuntu' } |
| 127 | +```` |
| 128 | + |
| 129 | +### 3. No Node Attribute Access |
| 130 | + |
| 131 | +YAML recipes cannot directly access node attributes or perform Ruby evaluations: |
| 132 | + |
| 133 | +````yaml |
| 134 | +# ❌ Cannot access node attributes dynamically |
| 135 | +resources: |
| 136 | + - type: "user" |
| 137 | + name: "webapp" |
| 138 | + # Cannot do: home "/home/#{node['webapp']['user']}" |
| 139 | + home: "/home/webapp" # Must be static |
| 140 | +```` |
| 141 | + |
| 142 | +### 4. No Resource Notifications |
| 143 | + |
| 144 | +Complex resource relationships and notifications cannot be expressed: |
| 145 | + |
| 146 | +````yaml |
| 147 | +# ❌ Cannot express notifications between resources |
| 148 | +resources: |
| 149 | + - type: "template" |
| 150 | + name: "/etc/nginx/nginx.conf" |
| 151 | + source: "nginx.conf.erb" |
| 152 | + # Cannot do: notifies :restart, "service[nginx]", :delayed |
| 153 | +```` |
| 154 | + |
| 155 | +### 5. No Include/Require Functionality |
| 156 | + |
| 157 | +YAML recipes cannot include other recipes or libraries: |
| 158 | + |
| 159 | +````yaml |
| 160 | +# ❌ Cannot include other recipes |
| 161 | +# include_recipe "cookbook::other_recipe" |
| 162 | +```` |
| 163 | + |
| 164 | +## Examples |
| 165 | + |
| 166 | +### Basic File Management |
| 167 | + |
| 168 | +````yaml |
| 169 | +--- |
| 170 | +resources: |
| 171 | + - type: "directory" |
| 172 | + name: "/opt/myapp" |
| 173 | + owner: "myapp" |
| 174 | + group: "myapp" |
| 175 | + mode: "0755" |
| 176 | + recursive: true |
| 177 | + |
| 178 | + - type: "file" |
| 179 | + name: "/opt/myapp/config.txt" |
| 180 | + content: "This is a configuration file" |
| 181 | + owner: "myapp" |
| 182 | + group: "myapp" |
| 183 | + mode: "0644" |
| 184 | +```` |
| 185 | + |
| 186 | +### Package and Service Management |
| 187 | + |
| 188 | +````yaml |
| 189 | +--- |
| 190 | +resources: |
| 191 | + - type: "package" |
| 192 | + name: "nginx" |
| 193 | + action: "install" |
| 194 | + |
| 195 | + - type: "package" |
| 196 | + name: "curl" |
| 197 | + action: "install" |
| 198 | + |
| 199 | + - type: "service" |
| 200 | + name: "nginx" |
| 201 | + action: ["enable", "start"] |
| 202 | +```` |
| 203 | + |
| 204 | +### User Management |
| 205 | + |
| 206 | +````yaml |
| 207 | +--- |
| 208 | +resources: |
| 209 | + - type: "group" |
| 210 | + name: "developers" |
| 211 | + gid: 3000 |
| 212 | + |
| 213 | + - type: "user" |
| 214 | + name: "alice" |
| 215 | + uid: 2001 |
| 216 | + gid: 3000 |
| 217 | + home: "/home/alice" |
| 218 | + shell: "/bin/bash" |
| 219 | + action: "create" |
| 220 | +```` |
| 221 | + |
| 222 | +### Template with Static Variables |
| 223 | + |
| 224 | +````yaml |
| 225 | +--- |
| 226 | +resources: |
| 227 | + - type: "template" |
| 228 | + name: "/etc/myapp/config.yml" |
| 229 | + source: "config.yml.erb" |
| 230 | + owner: "root" |
| 231 | + group: "root" |
| 232 | + mode: "0644" |
| 233 | + # Note: Variables must be static, cannot use node attributes |
| 234 | +```` |
| 235 | + |
| 236 | +## Working with YAML Recipes |
| 237 | + |
| 238 | +### Converting Between Ruby and YAML |
| 239 | + |
| 240 | +Chef provides a `knife yaml convert` command to convert YAML recipes to Ruby: |
| 241 | + |
| 242 | +```powershell |
| 243 | +knife yaml convert recipes/default.yml recipes/default.rb |
| 244 | +``` |
| 245 | + |
| 246 | +**Note**: Converting from Ruby to YAML is not supported due to the limitations of YAML format. |
| 247 | + |
| 248 | +### File Processing |
| 249 | + |
| 250 | +YAML recipes are processed by the `from_yaml_file` method in the `Chef::Recipe` class, which: |
| 251 | + |
| 252 | +1. Validates the file exists and is readable |
| 253 | +2. Checks for single document requirement |
| 254 | +3. Parses YAML using safe loading |
| 255 | +4. Validates the required `resources` structure |
| 256 | +5. Converts to internal hash representation |
| 257 | +6. Creates Chef resources using `declare_resource` |
| 258 | + |
| 259 | +## Best Practices |
| 260 | + |
| 261 | +### When to Use YAML Recipes |
| 262 | + |
| 263 | +YAML recipes are best suited for: |
| 264 | + |
| 265 | +- **Simple, static configurations** |
| 266 | +- **Declarative resource management** |
| 267 | +- **Teams preferring YAML over Ruby** |
| 268 | +- **Basic infrastructure as code** |
| 269 | + |
| 270 | +### When NOT to Use YAML Recipes |
| 271 | + |
| 272 | +Avoid YAML recipes when you need: |
| 273 | + |
| 274 | +- **Dynamic node attribute access** |
| 275 | +- **Conditional logic** |
| 276 | +- **Resource notifications** |
| 277 | +- **Complex data transformations** |
| 278 | +- **Integration with Ruby libraries** |
| 279 | +- **Advanced Chef DSL features** |
| 280 | + |
| 281 | +### Migration Strategy |
| 282 | + |
| 283 | +1. Start with simple, static resources in YAML |
| 284 | +2. Use Ruby recipes for complex logic |
| 285 | +3. Consider hybrid approach: YAML for simple resources, Ruby for complex ones |
| 286 | +4. Use `knife yaml convert` to understand Ruby equivalents |
| 287 | + |
| 288 | +## Error Handling |
| 289 | + |
| 290 | +Common errors when working with YAML recipes: |
| 291 | + |
| 292 | +### Missing Resources Key |
| 293 | + |
| 294 | +```text |
| 295 | +ArgumentError: YAML recipe 'recipes/default.yml' must contain a top-level 'resources' hash (YAML sequence), i.e. 'resources:' |
| 296 | +``` |
| 297 | + |
| 298 | +### Multiple Documents |
| 299 | + |
| 300 | +```text |
| 301 | +ArgumentError: YAML recipe 'recipes/default.yml' contains multiple documents, only one is supported |
| 302 | +``` |
| 303 | + |
| 304 | +### Ambiguous File Extensions |
| 305 | + |
| 306 | +```text |
| 307 | +Chef::Exceptions::AmbiguousYAMLFile: Found both default.yml and default.yaml in cookbook, update the cookbook to remove one |
| 308 | +``` |
| 309 | + |
| 310 | +## Conclusion |
| 311 | + |
| 312 | +YAML recipes provide a simplified way to define Chef resources for basic use cases. While they have significant limitations compared to Ruby recipes, they can be valuable for teams that prefer declarative YAML syntax and don't require advanced Chef DSL features. For complex scenarios involving dynamic logic, node attributes, or resource relationships, Ruby recipes remain the preferred approach. |
| 313 | + |
| 314 | +For most production environments, a hybrid approach using both YAML recipes for simple static configurations and Ruby recipes for complex logic provides the best balance of simplicity and functionality. |
0 commit comments