From 87e6091fc84d412fd7c5febc1c8c81fd3e4326e2 Mon Sep 17 00:00:00 2001 From: John McCrae Date: Wed, 20 Aug 2025 18:33:24 +0000 Subject: [PATCH 01/10] Adding docs for yaml recipes Signed-off-by: John McCrae --- content/recipes_in_yaml.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 content/recipes_in_yaml.md diff --git a/content/recipes_in_yaml.md b/content/recipes_in_yaml.md new file mode 100644 index 0000000000..e69de29bb2 From 769784875b18ab1d8fd3097d88fa9ccffbac9c45 Mon Sep 17 00:00:00 2001 From: John McCrae Date: Wed, 20 Aug 2025 18:35:06 +0000 Subject: [PATCH 02/10] Adding docs for yaml recipes Signed-off-by: John McCrae --- content/recipes_in_yaml.md | 314 +++++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) diff --git a/content/recipes_in_yaml.md b/content/recipes_in_yaml.md index e69de29bb2..7095aaab3b 100644 --- a/content/recipes_in_yaml.md +++ b/content/recipes_in_yaml.md @@ -0,0 +1,314 @@ +# Chef YAML Recipes: Complete Guide + +## Overview + +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. + +## Basic Structure + +YAML recipes must follow a specific structure: + +````yaml +--- +resources: + - type: "resource_type" + name: "resource_name" + property1: value1 + property2: value2 + - type: "another_resource_type" + name: "another_resource_name" + property1: value1 + property2: value2 +```` + +## File Naming and Location + +YAML recipes can be placed in the same locations as Ruby recipes: + +- **Standard recipe location**: `cookbooks/mycookbook/recipes/default.yml` or `cookbooks/mycookbook/recipes/default.yaml` +- **Named recipes**: `cookbooks/mycookbook/recipes/web.yml` or `cookbooks/mycookbook/recipes/database.yaml` +- **Root-level recipe alias**: `cookbooks/mycookbook/recipe.yml` or `cookbooks/mycookbook/recipe.yaml` (acts as default recipe) + +### File Extension Support + +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. + +## Required Structure and Restrictions + +### 1. Top-Level Resources Hash + +Every YAML recipe **must** contain a top-level `resources` key that contains an array of resource declarations: + +````yaml +# ✅ CORRECT +--- +resources: + - type: "file" + name: "/tmp/hello.txt" + content: "Hello World" + +# ❌ INCORRECT - Missing resources key +--- +- type: "file" + name: "/tmp/hello.txt" + content: "Hello World" + +# ❌ INCORRECT - Wrong structure +--- +files: + - type: "file" + name: "/tmp/hello.txt" + content: "Hello World" +```` + +### 2. Resource Declaration Format + +Each resource in the array must have: + +- **`type`**: The Chef resource type (string) +- **`name`**: The resource name/identifier (string) +- **Additional properties**: Resource-specific properties as key-value pairs + +````yaml +resources: + - type: "package" + name: "nginx" + action: "install" + version: "1.18.0" + - type: "service" + name: "nginx" + action: ["enable", "start"] +```` + +### 3. Single Document Limitation + +YAML recipes support only **one YAML document** per file. Multiple documents separated by `---` are not allowed: + +````yaml +# ❌ INCORRECT - Multiple documents not supported +--- +resources: + - type: "file" + name: "/tmp/file1.txt" +--- +resources: + - type: "file" + name: "/tmp/file2.txt" +```` + +## Major Limitations + +### 1. No Ruby Code Blocks + +YAML recipes cannot contain Ruby code blocks, which significantly limits their functionality compared to Ruby recipes: + +````ruby +# ❌ Cannot be expressed in YAML - Ruby blocks not supported +template "/etc/nginx/nginx.conf" do + source "nginx.conf.erb" + variables({ + worker_processes: node['cpu']['total'] + }) + notifies :restart, "service[nginx]", :delayed + only_if { node['platform'] == 'ubuntu' } +end +```` + +### 2. No Conditional Logic + +YAML recipes cannot include conditional logic like `if`, `unless`, `only_if`, or `not_if` with Ruby expressions: + +````yaml +# ❌ Cannot include complex conditionals +resources: + - type: "package" + name: "nginx" + # Cannot do: only_if { node['platform'] == 'ubuntu' } +```` + +### 3. No Node Attribute Access + +YAML recipes cannot directly access node attributes or perform Ruby evaluations: + +````yaml +# ❌ Cannot access node attributes dynamically +resources: + - type: "user" + name: "webapp" + # Cannot do: home "/home/#{node['webapp']['user']}" + home: "/home/webapp" # Must be static +```` + +### 4. No Resource Notifications + +Complex resource relationships and notifications cannot be expressed: + +````yaml +# ❌ Cannot express notifications between resources +resources: + - type: "template" + name: "/etc/nginx/nginx.conf" + source: "nginx.conf.erb" + # Cannot do: notifies :restart, "service[nginx]", :delayed +```` + +### 5. No Include/Require Functionality + +YAML recipes cannot include other recipes or libraries: + +````yaml +# ❌ Cannot include other recipes +# include_recipe "cookbook::other_recipe" +```` + +## Examples + +### Basic File Management + +````yaml +--- +resources: + - type: "directory" + name: "/opt/myapp" + owner: "myapp" + group: "myapp" + mode: "0755" + recursive: true + + - type: "file" + name: "/opt/myapp/config.txt" + content: "This is a configuration file" + owner: "myapp" + group: "myapp" + mode: "0644" +```` + +### Package and Service Management + +````yaml +--- +resources: + - type: "package" + name: "nginx" + action: "install" + + - type: "package" + name: "curl" + action: "install" + + - type: "service" + name: "nginx" + action: ["enable", "start"] +```` + +### User Management + +````yaml +--- +resources: + - type: "group" + name: "developers" + gid: 3000 + + - type: "user" + name: "alice" + uid: 2001 + gid: 3000 + home: "/home/alice" + shell: "/bin/bash" + action: "create" +```` + +### Template with Static Variables + +````yaml +--- +resources: + - type: "template" + name: "/etc/myapp/config.yml" + source: "config.yml.erb" + owner: "root" + group: "root" + mode: "0644" + # Note: Variables must be static, cannot use node attributes +```` + +## Working with YAML Recipes + +### Converting Between Ruby and YAML + +Chef provides a `knife yaml convert` command to convert YAML recipes to Ruby: + +```powershell +knife yaml convert recipes/default.yml recipes/default.rb +``` + +**Note**: Converting from Ruby to YAML is not supported due to the limitations of YAML format. + +### File Processing + +YAML recipes are processed by the `from_yaml_file` method in the `Chef::Recipe` class, which: + +1. Validates the file exists and is readable +2. Checks for single document requirement +3. Parses YAML using safe loading +4. Validates the required `resources` structure +5. Converts to internal hash representation +6. Creates Chef resources using `declare_resource` + +## Best Practices + +### When to Use YAML Recipes + +YAML recipes are best suited for: + +- **Simple, static configurations** +- **Declarative resource management** +- **Teams preferring YAML over Ruby** +- **Basic infrastructure as code** + +### When NOT to Use YAML Recipes + +Avoid YAML recipes when you need: + +- **Dynamic node attribute access** +- **Conditional logic** +- **Resource notifications** +- **Complex data transformations** +- **Integration with Ruby libraries** +- **Advanced Chef DSL features** + +### Migration Strategy + +1. Start with simple, static resources in YAML +2. Use Ruby recipes for complex logic +3. Consider hybrid approach: YAML for simple resources, Ruby for complex ones +4. Use `knife yaml convert` to understand Ruby equivalents + +## Error Handling + +Common errors when working with YAML recipes: + +### Missing Resources Key + +```text +ArgumentError: YAML recipe 'recipes/default.yml' must contain a top-level 'resources' hash (YAML sequence), i.e. 'resources:' +``` + +### Multiple Documents + +```text +ArgumentError: YAML recipe 'recipes/default.yml' contains multiple documents, only one is supported +``` + +### Ambiguous File Extensions + +```text +Chef::Exceptions::AmbiguousYAMLFile: Found both default.yml and default.yaml in cookbook, update the cookbook to remove one +``` + +## Conclusion + +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. + +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. From 1b67400dd35bb88402309b29504ef38395a1898d Mon Sep 17 00:00:00 2001 From: Ian Maddaus Date: Thu, 21 Aug 2025 14:23:58 -0400 Subject: [PATCH 03/10] Quick fixes Signed-off-by: Ian Maddaus --- content/debug.md | 2 +- .../{recipes_in_yaml.md => recipes_yaml.md} | 128 ++++++++++-------- 2 files changed, 69 insertions(+), 61 deletions(-) rename content/{recipes_in_yaml.md => recipes_yaml.md} (74%) diff --git a/content/debug.md b/content/debug.md index 26943969dc..f27835724b 100644 --- a/content/debug.md +++ b/content/debug.md @@ -9,7 +9,7 @@ aliases = ["/debug.html"] title = "Debug Recipes, Client Runs" identifier = "chef_infra/cookbook_reference/recipes/debug.md Debug Recipes, Client Runs" parent = "chef_infra/cookbook_reference/recipes" - weight = 20 + weight = 30 +++ Elements of good approaches to building cookbooks and recipes that are diff --git a/content/recipes_in_yaml.md b/content/recipes_yaml.md similarity index 74% rename from content/recipes_in_yaml.md rename to content/recipes_yaml.md index 7095aaab3b..b53d91105e 100644 --- a/content/recipes_in_yaml.md +++ b/content/recipes_yaml.md @@ -1,14 +1,28 @@ -# Chef YAML Recipes: Complete Guide ++++ +title = "About YAML Recipes" +draft = false +gh_repo = "chef-web-docs" + +[menu] + [menu.infra] + title = " YAML Recipes" + identifier = "chef_infra/cookbook_reference/recipes/YAML Recipes" + parent = "chef_infra/cookbook_reference/recipes" + weight = 20 ++++ + +YAML recipes provide an alternative way to define Chef Infra resources using YAML syntax instead of Ruby. +This feature was introduced to make Chef Infra recipes more accessible to users who are more comfortable with declarative YAML syntax than Ruby code. -## Overview +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. -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. +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. ## Basic Structure YAML recipes must follow a specific structure: -````yaml +```yaml --- resources: - type: "resource_type" @@ -19,15 +33,15 @@ resources: name: "another_resource_name" property1: value1 property2: value2 -```` +``` ## File Naming and Location YAML recipes can be placed in the same locations as Ruby recipes: -- **Standard recipe location**: `cookbooks/mycookbook/recipes/default.yml` or `cookbooks/mycookbook/recipes/default.yaml` -- **Named recipes**: `cookbooks/mycookbook/recipes/web.yml` or `cookbooks/mycookbook/recipes/database.yaml` -- **Root-level recipe alias**: `cookbooks/mycookbook/recipe.yml` or `cookbooks/mycookbook/recipe.yaml` (acts as default recipe) +- **Standard recipe location**: `cookbooks/cookbook_name/recipes/default.yml` or `cookbooks/cookbook_name/recipes/default.yaml` +- **Named recipes**: `cookbooks/cookbook_name/recipes/web.yml` or `cookbooks/cookbook_name/recipes/database.yaml` +- **Root-level recipe alias**: `cookbooks/cookbook_name/recipe.yml` or `cookbooks/cookbook_name/recipe.yaml` (acts as default recipe) ### File Extension Support @@ -35,11 +49,11 @@ Both `.yml` and `.yaml` extensions are supported. However, if both `default.yml` ## Required Structure and Restrictions -### 1. Top-Level Resources Hash +### Top-Level Resources Hash Every YAML recipe **must** contain a top-level `resources` key that contains an array of resource declarations: -````yaml +```yaml # ✅ CORRECT --- resources: @@ -59,9 +73,9 @@ files: - type: "file" name: "/tmp/hello.txt" content: "Hello World" -```` +``` -### 2. Resource Declaration Format +### Resource Declaration Format Each resource in the array must have: @@ -69,7 +83,7 @@ Each resource in the array must have: - **`name`**: The resource name/identifier (string) - **Additional properties**: Resource-specific properties as key-value pairs -````yaml +```yaml resources: - type: "package" name: "nginx" @@ -78,13 +92,13 @@ resources: - type: "service" name: "nginx" action: ["enable", "start"] -```` +``` -### 3. Single Document Limitation +### Single Document Limitation -YAML recipes support only **one YAML document** per file. Multiple documents separated by `---` are not allowed: +YAML recipes support only **one YAML document** in each file. Multiple documents separated by `---` aren't allowed: -````yaml +```yaml # ❌ INCORRECT - Multiple documents not supported --- resources: @@ -94,15 +108,15 @@ resources: resources: - type: "file" name: "/tmp/file2.txt" -```` +``` -## Major Limitations +## YAML recipe limitations -### 1. No Ruby Code Blocks +### No Ruby Code Blocks -YAML recipes cannot contain Ruby code blocks, which significantly limits their functionality compared to Ruby recipes: +YAML recipes can't contain Ruby code blocks, which significantly limits their functionality compared to Ruby recipes: -````ruby +```ruby # ❌ Cannot be expressed in YAML - Ruby blocks not supported template "/etc/nginx/nginx.conf" do source "nginx.conf.erb" @@ -112,60 +126,60 @@ template "/etc/nginx/nginx.conf" do notifies :restart, "service[nginx]", :delayed only_if { node['platform'] == 'ubuntu' } end -```` +``` -### 2. No Conditional Logic +### No Conditional Logic -YAML recipes cannot include conditional logic like `if`, `unless`, `only_if`, or `not_if` with Ruby expressions: +YAML recipes can't include conditional logic like `if`, `unless`, `only_if`, or `not_if` with Ruby expressions: -````yaml +```yaml # ❌ Cannot include complex conditionals resources: - type: "package" name: "nginx" # Cannot do: only_if { node['platform'] == 'ubuntu' } -```` +``` -### 3. No Node Attribute Access +### No Node Attribute Access -YAML recipes cannot directly access node attributes or perform Ruby evaluations: +YAML recipes can't directly access node attributes or perform Ruby evaluations: -````yaml +```yaml # ❌ Cannot access node attributes dynamically resources: - type: "user" name: "webapp" # Cannot do: home "/home/#{node['webapp']['user']}" home: "/home/webapp" # Must be static -```` +``` -### 4. No Resource Notifications +### No Resource Notifications -Complex resource relationships and notifications cannot be expressed: +Complex resource relationships and notifications can't be expressed: -````yaml +```yaml # ❌ Cannot express notifications between resources resources: - type: "template" name: "/etc/nginx/nginx.conf" source: "nginx.conf.erb" # Cannot do: notifies :restart, "service[nginx]", :delayed -```` +``` -### 5. No Include/Require Functionality +### No Include/Require Functionality -YAML recipes cannot include other recipes or libraries: +YAML recipes can't include other recipes or libraries: -````yaml +```yaml # ❌ Cannot include other recipes # include_recipe "cookbook::other_recipe" -```` +``` ## Examples ### Basic File Management -````yaml +```yaml --- resources: - type: "directory" @@ -174,42 +188,42 @@ resources: group: "myapp" mode: "0755" recursive: true - + - type: "file" name: "/opt/myapp/config.txt" content: "This is a configuration file" owner: "myapp" group: "myapp" mode: "0644" -```` +``` ### Package and Service Management -````yaml +```yaml --- resources: - type: "package" name: "nginx" action: "install" - + - type: "package" name: "curl" action: "install" - + - type: "service" name: "nginx" action: ["enable", "start"] -```` +``` ### User Management -````yaml +```yaml --- resources: - type: "group" name: "developers" gid: 3000 - + - type: "user" name: "alice" uid: 2001 @@ -217,11 +231,11 @@ resources: home: "/home/alice" shell: "/bin/bash" action: "create" -```` +``` ### Template with Static Variables -````yaml +```yaml --- resources: - type: "template" @@ -230,8 +244,8 @@ resources: owner: "root" group: "root" mode: "0644" - # Note: Variables must be static, cannot use node attributes -```` + # Note: Variables must be static, can't use node attributes +``` ## Working with YAML Recipes @@ -243,7 +257,7 @@ Chef provides a `knife yaml convert` command to convert YAML recipes to Ruby: knife yaml convert recipes/default.yml recipes/default.rb ``` -**Note**: Converting from Ruby to YAML is not supported due to the limitations of YAML format. +**Note**: Converting from Ruby to YAML isn't supported due to the limitations of YAML format. ### File Processing @@ -285,7 +299,7 @@ Avoid YAML recipes when you need: 3. Consider hybrid approach: YAML for simple resources, Ruby for complex ones 4. Use `knife yaml convert` to understand Ruby equivalents -## Error Handling +## Troubleshooting Common errors when working with YAML recipes: @@ -306,9 +320,3 @@ ArgumentError: YAML recipe 'recipes/default.yml' contains multiple documents, on ```text Chef::Exceptions::AmbiguousYAMLFile: Found both default.yml and default.yaml in cookbook, update the cookbook to remove one ``` - -## Conclusion - -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. - -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. From 10122759ee6525466d13e6e308f0e53a12caab44 Mon Sep 17 00:00:00 2001 From: Ian Maddaus Date: Thu, 21 Aug 2025 15:34:34 -0400 Subject: [PATCH 04/10] Editing Signed-off-by: Ian Maddaus --- content/recipes_yaml.md | 347 ++++++++++++++++++---------------------- 1 file changed, 152 insertions(+), 195 deletions(-) diff --git a/content/recipes_yaml.md b/content/recipes_yaml.md index b53d91105e..fb6af052cb 100644 --- a/content/recipes_yaml.md +++ b/content/recipes_yaml.md @@ -1,120 +1,184 @@ +++ -title = "About YAML Recipes" +title = "About YAML recipes" draft = false gh_repo = "chef-web-docs" [menu] [menu.infra] - title = " YAML Recipes" - identifier = "chef_infra/cookbook_reference/recipes/YAML Recipes" + title = "YAML recipes" + identifier = "chef_infra/cookbook_reference/recipes/YAML recipes" parent = "chef_infra/cookbook_reference/recipes" weight = 20 +++ -YAML recipes provide an alternative way to define Chef Infra resources using YAML syntax instead of Ruby. -This feature was introduced to make Chef Infra recipes more accessible to users who are more comfortable with declarative YAML syntax than Ruby code. +YAML recipes let you define Chef Infra resources using YAML syntax instead of Ruby. This feature makes Chef Infra recipes more accessible to users who prefer declarative YAML syntax over Ruby code. -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. +YAML recipes simplify defining Chef resources for basic use cases. While they have significant limitations compared to Ruby recipes, they're valuable for teams that prefer YAML syntax and don't need advanced Chef DSL features. For complex scenarios involving dynamic logic, node attributes, or resource relationships, use Ruby recipes. -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. +For most production environments, use a hybrid approach: YAML recipes for simple static configurations and Ruby recipes for complex logic. This approach balances simplicity and functionality. -## Basic Structure +## Use cases -YAML recipes must follow a specific structure: +Use YAML recipes for: -```yaml ---- -resources: - - type: "resource_type" - name: "resource_name" - property1: value1 - property2: value2 - - type: "another_resource_type" - name: "another_resource_name" - property1: value1 - property2: value2 -``` - -## File Naming and Location +- Simple, static configurations +- Declarative resource management +- Teams preferring YAML over Ruby +- Basic infrastructure as code -YAML recipes can be placed in the same locations as Ruby recipes: - -- **Standard recipe location**: `cookbooks/cookbook_name/recipes/default.yml` or `cookbooks/cookbook_name/recipes/default.yaml` -- **Named recipes**: `cookbooks/cookbook_name/recipes/web.yml` or `cookbooks/cookbook_name/recipes/database.yaml` -- **Root-level recipe alias**: `cookbooks/cookbook_name/recipe.yml` or `cookbooks/cookbook_name/recipe.yaml` (acts as default recipe) - -### File Extension Support - -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. +Avoid YAML recipes when you need: -## Required Structure and Restrictions +- Dynamic node attribute access +- Conditional logic +- Resource notifications +- Complex data transformations +- Integration with Ruby libraries +- Advanced Chef DSL features + +## Create a YAML recipe + +To create a YAML recipe, follow these steps: + +1. Create a YAML file for your recipe in the same locations as Ruby recipes: + + - Standard recipe location: + - `cookbooks/cookbook_name/recipes/default.yml` + - `cookbooks/cookbook_name/recipes/default.yaml` + - Named recipes: + - `cookbooks/cookbook_name/recipes/web.yml` + - `cookbooks/cookbook_name/recipes/database.yaml` + - Root-level recipe alias (acts as the default recipe): + - `cookbooks/cookbook_name/recipe.yml` + - `cookbooks/cookbook_name/recipe.yaml` + + Chef Infra supports YAML recipes with both `.yml` and `.yaml` extensions. + +1. Define your recipe using the following structure. + It must have a top-level `resources` key containing an array of resource declarations. For example: + + ```yaml + --- + resources: + - type: "resource_type" + name: "resource_name" + property1: value1 + property2: value2 + - type: "another_resource_type" + name: "another_resource_name" + property1: value1 + property2: value2 + ``` + +1. Declare each resource as an array that defines the following: + + - `type`: The Chef resource type (string) + - `name`: The resource name/identifier (string) + - resource-specific actions and properties as key-value pairs + + For example: + + ```yaml + resources: + - type: "package" + name: "nginx" + action: "install" + version: "1.18.0" + - type: "service" + name: "nginx" + action: ["enable", "start"] + ``` + + In this example, the [`package` resource]({{< relref "/resources/package/" >}}) uses the `install` action to install Nginx version 1.18.0 and the [`service` resource]({{< relref "/resources/service/" >}}) uses the `enable` and `start` actions to start up Nginx. -### Top-Level Resources Hash +## Examples -Every YAML recipe **must** contain a top-level `resources` key that contains an array of resource declarations: +### Basic file management ```yaml -# ✅ CORRECT --- resources: - - type: "file" - name: "/tmp/hello.txt" - content: "Hello World" - -# ❌ INCORRECT - Missing resources key ---- -- type: "file" - name: "/tmp/hello.txt" - content: "Hello World" + - type: "directory" + name: "/opt/myapp" + owner: "myapp" + group: "myapp" + mode: "0755" + recursive: true -# ❌ INCORRECT - Wrong structure ---- -files: - type: "file" - name: "/tmp/hello.txt" - content: "Hello World" + name: "/opt/myapp/config.txt" + content: "This is a configuration file" + owner: "myapp" + group: "myapp" + mode: "0644" ``` -### Resource Declaration Format - -Each resource in the array must have: - -- **`type`**: The Chef resource type (string) -- **`name`**: The resource name/identifier (string) -- **Additional properties**: Resource-specific properties as key-value pairs +### Package and service management ```yaml +--- resources: - type: "package" name: "nginx" action: "install" - version: "1.18.0" + + - type: "package" + name: "curl" + action: "install" + - type: "service" name: "nginx" action: ["enable", "start"] ``` -### Single Document Limitation - -YAML recipes support only **one YAML document** in each file. Multiple documents separated by `---` aren't allowed: +### User management ```yaml -# ❌ INCORRECT - Multiple documents not supported --- resources: - - type: "file" - name: "/tmp/file1.txt" + - type: "group" + name: "developers" + gid: 3000 + + - type: "user" + name: "alice" + uid: 2001 + gid: 3000 + home: "/home/alice" + shell: "/bin/bash" + action: "create" +``` + +### Template with static variables + +```yaml --- resources: - - type: "file" - name: "/tmp/file2.txt" + - type: "template" + name: "/etc/myapp/config.yml" + source: "config.yml.erb" + owner: "root" + group: "root" + mode: "0644" + # Note: Variables must be static, can't use node attributes ``` +## Convert a YAML recipe to Ruby + +Use the `knife yaml convert` command to convert YAML recipes to Ruby: + +```shell +knife yaml convert recipes/default.yml recipes/default.rb +``` + +Converting from Ruby to YAML isn't supported due to YAML's limitations. + ## YAML recipe limitations -### No Ruby Code Blocks +Chef Infra YAML recipes have the following limitation. + +### No Ruby code blocks -YAML recipes can't contain Ruby code blocks, which significantly limits their functionality compared to Ruby recipes: +YAML recipes can't include Ruby code blocks, which limits their functionality compared to Ruby recipes: ```ruby # ❌ Cannot be expressed in YAML - Ruby blocks not supported @@ -128,7 +192,7 @@ template "/etc/nginx/nginx.conf" do end ``` -### No Conditional Logic +### No conditional logic YAML recipes can't include conditional logic like `if`, `unless`, `only_if`, or `not_if` with Ruby expressions: @@ -140,7 +204,7 @@ resources: # Cannot do: only_if { node['platform'] == 'ubuntu' } ``` -### No Node Attribute Access +### No node attribute access YAML recipes can't directly access node attributes or perform Ruby evaluations: @@ -153,9 +217,9 @@ resources: home: "/home/webapp" # Must be static ``` -### No Resource Notifications +### No resource notifications -Complex resource relationships and notifications can't be expressed: +YAML recipes can't express complex resource relationships and notifications: ```yaml # ❌ Cannot express notifications between resources @@ -166,7 +230,7 @@ resources: # Cannot do: notifies :restart, "service[nginx]", :delayed ``` -### No Include/Require Functionality +### No include/require functionality YAML recipes can't include other recipes or libraries: @@ -175,147 +239,40 @@ YAML recipes can't include other recipes or libraries: # include_recipe "cookbook::other_recipe" ``` -## Examples +## Troubleshooting -### Basic File Management +### Missing `resources` key -```yaml ---- -resources: - - type: "directory" - name: "/opt/myapp" - owner: "myapp" - group: "myapp" - mode: "0755" - recursive: true +Chef Infra Client returns this error if a recipe is missing the top-level `resources` hash. - - type: "file" - name: "/opt/myapp/config.txt" - content: "This is a configuration file" - owner: "myapp" - group: "myapp" - mode: "0644" +```text +ArgumentError: YAML recipe 'recipes/default.yml' must contain a top-level 'resources' hash (YAML sequence), i.e. 'resources:' ``` -### Package and Service Management - -```yaml ---- -resources: - - type: "package" - name: "nginx" - action: "install" +### Single document limitation - - type: "package" - name: "curl" - action: "install" - - - type: "service" - name: "nginx" - action: ["enable", "start"] -``` - -### User Management +YAML recipes support only one YAML document in each file. Multiple documents separated by `---` aren't allowed: ```yaml --- resources: - - type: "group" - name: "developers" - gid: 3000 - - - type: "user" - name: "alice" - uid: 2001 - gid: 3000 - home: "/home/alice" - shell: "/bin/bash" - action: "create" -``` - -### Template with Static Variables - -```yaml + - type: "file" + name: "/tmp/file1.txt" --- resources: - - type: "template" - name: "/etc/myapp/config.yml" - source: "config.yml.erb" - owner: "root" - group: "root" - mode: "0644" - # Note: Variables must be static, can't use node attributes -``` - -## Working with YAML Recipes - -### Converting Between Ruby and YAML - -Chef provides a `knife yaml convert` command to convert YAML recipes to Ruby: - -```powershell -knife yaml convert recipes/default.yml recipes/default.rb -``` - -**Note**: Converting from Ruby to YAML isn't supported due to the limitations of YAML format. - -### File Processing - -YAML recipes are processed by the `from_yaml_file` method in the `Chef::Recipe` class, which: - -1. Validates the file exists and is readable -2. Checks for single document requirement -3. Parses YAML using safe loading -4. Validates the required `resources` structure -5. Converts to internal hash representation -6. Creates Chef resources using `declare_resource` - -## Best Practices - -### When to Use YAML Recipes - -YAML recipes are best suited for: - -- **Simple, static configurations** -- **Declarative resource management** -- **Teams preferring YAML over Ruby** -- **Basic infrastructure as code** - -### When NOT to Use YAML Recipes - -Avoid YAML recipes when you need: - -- **Dynamic node attribute access** -- **Conditional logic** -- **Resource notifications** -- **Complex data transformations** -- **Integration with Ruby libraries** -- **Advanced Chef DSL features** - -### Migration Strategy - -1. Start with simple, static resources in YAML -2. Use Ruby recipes for complex logic -3. Consider hybrid approach: YAML for simple resources, Ruby for complex ones -4. Use `knife yaml convert` to understand Ruby equivalents - -## Troubleshooting - -Common errors when working with YAML recipes: - -### Missing Resources Key - -```text -ArgumentError: YAML recipe 'recipes/default.yml' must contain a top-level 'resources' hash (YAML sequence), i.e. 'resources:' + - type: "file" + name: "/tmp/file2.txt" ``` -### Multiple Documents +Chef Infra Client returns the following error with multiple documents: ```text ArgumentError: YAML recipe 'recipes/default.yml' contains multiple documents, only one is supported ``` -### Ambiguous File Extensions +### Ambiguous file extensions + +Chef Infra Client returns this error if multiple recipes have the same filename but a different file extension. For example, `default.yaml` and `default.yml`. ```text Chef::Exceptions::AmbiguousYAMLFile: Found both default.yml and default.yaml in cookbook, update the cookbook to remove one From 5238cf0ac30e01e703ee3f641c73a162c2ed9b80 Mon Sep 17 00:00:00 2001 From: Ian Maddaus Date: Thu, 21 Aug 2025 15:52:21 -0400 Subject: [PATCH 05/10] More editing Signed-off-by: Ian Maddaus --- content/recipes_yaml.md | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/content/recipes_yaml.md b/content/recipes_yaml.md index fb6af052cb..a86093528c 100644 --- a/content/recipes_yaml.md +++ b/content/recipes_yaml.md @@ -53,23 +53,7 @@ To create a YAML recipe, follow these steps: Chef Infra supports YAML recipes with both `.yml` and `.yaml` extensions. -1. Define your recipe using the following structure. - It must have a top-level `resources` key containing an array of resource declarations. For example: - - ```yaml - --- - resources: - - type: "resource_type" - name: "resource_name" - property1: value1 - property2: value2 - - type: "another_resource_type" - name: "another_resource_name" - property1: value1 - property2: value2 - ``` - -1. Declare each resource as an array that defines the following: +1. Define your recipe with a top-level `resources` key containing an array of resources where each item has the following: - `type`: The Chef resource type (string) - `name`: The resource name/identifier (string) @@ -88,7 +72,10 @@ To create a YAML recipe, follow these steps: action: ["enable", "start"] ``` - In this example, the [`package` resource]({{< relref "/resources/package/" >}}) uses the `install` action to install Nginx version 1.18.0 and the [`service` resource]({{< relref "/resources/service/" >}}) uses the `enable` and `start` actions to start up Nginx. + In this example: + + - the [`package` resource]({{< relref "/resources/package/" >}}) uses the `install` action and the `version` property to install Nginx 1.18.0. + - the [`service` resource]({{< relref "/resources/service/" >}}) uses the `enable` and `start` actions to enable and start up Nginx. ## Examples @@ -264,7 +251,7 @@ resources: name: "/tmp/file2.txt" ``` -Chef Infra Client returns the following error with multiple documents: +Chef Infra Client returns the following error with multiple documents in one file: ```text ArgumentError: YAML recipe 'recipes/default.yml' contains multiple documents, only one is supported From 8fc401c9659d61f54c115e9172a5fffb8b57269e Mon Sep 17 00:00:00 2001 From: Ian Maddaus Date: Thu, 21 Aug 2025 17:04:05 -0400 Subject: [PATCH 06/10] More editing Signed-off-by: Ian Maddaus --- content/recipes_yaml.md | 69 ++++++++++++++---------- tools/vale/Microsoft/HeadingAcronyms.yml | 5 ++ tools/vale/Microsoft/Headings.yml | 3 ++ 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/content/recipes_yaml.md b/content/recipes_yaml.md index a86093528c..7f79d53a3b 100644 --- a/content/recipes_yaml.md +++ b/content/recipes_yaml.md @@ -17,24 +17,6 @@ YAML recipes simplify defining Chef resources for basic use cases. While they ha For most production environments, use a hybrid approach: YAML recipes for simple static configurations and Ruby recipes for complex logic. This approach balances simplicity and functionality. -## Use cases - -Use YAML recipes for: - -- Simple, static configurations -- Declarative resource management -- Teams preferring YAML over Ruby -- Basic infrastructure as code - -Avoid YAML recipes when you need: - -- Dynamic node attribute access -- Conditional logic -- Resource notifications -- Complex data transformations -- Integration with Ruby libraries -- Advanced Chef DSL features - ## Create a YAML recipe To create a YAML recipe, follow these steps: @@ -75,32 +57,36 @@ To create a YAML recipe, follow these steps: In this example: - the [`package` resource]({{< relref "/resources/package/" >}}) uses the `install` action and the `version` property to install Nginx 1.18.0. - - the [`service` resource]({{< relref "/resources/service/" >}}) uses the `enable` and `start` actions to enable and start up Nginx. + - the [`service` resource]({{< relref "/resources/service/" >}}) uses the `enable` and `start` actions to enable and start Nginx. ## Examples ### Basic file management +Use the [`directory` resource]({{< relref "/resources/directory">}}) to create the `/opt/app_name` directory and apply owner and group permissions to the directory. Use the [`file` resource]({{< relref "/resources/">}}) to create the `/opt/app_name/config.txt` file, add text to the file, and apply file owner and group permissions to the file. + ```yaml --- resources: - type: "directory" - name: "/opt/myapp" - owner: "myapp" - group: "myapp" + name: "/opt/app_name" + owner: "app_name" + group: "app_name" mode: "0755" recursive: true - type: "file" - name: "/opt/myapp/config.txt" + name: "/opt/app_name/config.txt" content: "This is a configuration file" - owner: "myapp" - group: "myapp" + owner: "app_name" + group: "app_name" mode: "0644" ``` ### Package and service management +Use the [`package` resource]({{< relref "/resources/package">}}) to install Nginx and curl. Then use the [`service` resource]({{< relref "/resources/service">}}) to enable and start Nginx. + ```yaml --- resources: @@ -119,6 +105,8 @@ resources: ### User management +Use the [`group` resource]({{< relref "/resources/group">}}) to create a group called "developers" and the [`user` resource]({{< relref "/resources/">}}) to create a user, give them properties, and assign them to the developers group. + ```yaml --- resources: @@ -137,16 +125,39 @@ resources: ### Template with static variables +Use the [`template` resource]({{< relref "/resources/template">}}) create the `/etc/app_name/config.yml` file using the `config.yml.erb` template. + ```yaml --- resources: - type: "template" - name: "/etc/myapp/config.yml" + name: "/etc/app_name/config.yml" source: "config.yml.erb" owner: "root" group: "root" mode: "0644" - # Note: Variables must be static, can't use node attributes +``` + +### Guards + +Some common resource functionality is also supported, as long as the value of a property can be expressed as one of the four primitive types (string, integer, boolean, array). That means it's possible to use [`only_if` or `not_if` guards]({{< relref "/resource_common#guards" >}}) as long as they shell out to Bash or PowerShell and aren't passed a Ruby block. + +For example, this is supported: + +```yaml +resources: +- type: "directory" + name: "/var/www/html" + only_if: "which apache2" +``` + +Ruby blocks aren't supported: + +```yaml +resources: +- type: "directory" + name: "/var/www/html" + only_if: "{ ::File.exist?('/usr/sbin/apache2') }" ``` ## Convert a YAML recipe to Ruby @@ -217,7 +228,7 @@ resources: # Cannot do: notifies :restart, "service[nginx]", :delayed ``` -### No include/require functionality +### No include or require functionality YAML recipes can't include other recipes or libraries: @@ -259,7 +270,7 @@ ArgumentError: YAML recipe 'recipes/default.yml' contains multiple documents, on ### Ambiguous file extensions -Chef Infra Client returns this error if multiple recipes have the same filename but a different file extension. For example, `default.yaml` and `default.yml`. +Chef Infra Client returns this error if two recipes have the same filename with different file extensions. For example, `default.yaml` and `default.yml`. ```text Chef::Exceptions::AmbiguousYAMLFile: Found both default.yml and default.yaml in cookbook, update the cookbook to remove one diff --git a/tools/vale/Microsoft/HeadingAcronyms.yml b/tools/vale/Microsoft/HeadingAcronyms.yml index 9dc3b6c2de..811899c0e9 100644 --- a/tools/vale/Microsoft/HeadingAcronyms.yml +++ b/tools/vale/Microsoft/HeadingAcronyms.yml @@ -5,3 +5,8 @@ level: warning scope: heading tokens: - '[A-Z]{2,4}' +exceptions: + - API + - YAML + - TOML + - JSON diff --git a/tools/vale/Microsoft/Headings.yml b/tools/vale/Microsoft/Headings.yml index 63624edc1b..b96c0a7ad8 100644 --- a/tools/vale/Microsoft/Headings.yml +++ b/tools/vale/Microsoft/Headings.yml @@ -14,15 +14,18 @@ exceptions: - Docker - Emmet - I + - JSON - Kubernetes - Linux - macOS - Marketplace - MongoDB - REPL + - Ruby - Studio - TypeScript - URLs - Visual - VS - Windows + - YAML From a69ab05a98d15b28867e5d26d5ffe1eae94018d2 Mon Sep 17 00:00:00 2001 From: Ian Maddaus Date: Thu, 21 Aug 2025 19:26:03 -0400 Subject: [PATCH 07/10] Add JSON recipes Signed-off-by: Ian Maddaus --- content/recipes.md | 6 + content/recipes_json_yaml.md | 494 ++++++++++++++++++ content/recipes_yaml.md | 277 ---------- .../reusable/md/recipes_yaml_json_overview.md | 5 + 4 files changed, 505 insertions(+), 277 deletions(-) create mode 100644 content/recipes_json_yaml.md delete mode 100644 content/recipes_yaml.md create mode 100644 content/reusable/md/recipes_yaml_json_overview.md diff --git a/content/recipes.md b/content/recipes.md index fd0d838fdc..1328d925a4 100644 --- a/content/recipes.md +++ b/content/recipes.md @@ -14,6 +14,12 @@ aliases = ["/recipes.html", "essentials_cookbook_recipes.html"] {{< readfile file="content/reusable/md/cookbooks_recipe.md" >}} +## YAML and JSON recipes + +{{< readfile file = "content/reusable/md/recipes_yaml_json_overview.md" >}} + +See the [YAML and JSON recipe documentation]({{< relref "recipes_json_yaml" >}}) for more information. + ## Recipe Attributes {{< readfile file="content/reusable/md/cookbooks_attribute.md" >}} diff --git a/content/recipes_json_yaml.md b/content/recipes_json_yaml.md new file mode 100644 index 0000000000..64d24b5acf --- /dev/null +++ b/content/recipes_json_yaml.md @@ -0,0 +1,494 @@ ++++ +title = "Chef Infra JSON and YAML recipes" +draft = false +gh_repo = "chef-web-docs" + +[menu] + [menu.infra] + title = "JSON/YAML recipes" + identifier = "chef_infra/cookbook_reference/recipes/YAML recipes" + parent = "chef_infra/cookbook_reference/recipes" + weight = 20 ++++ + +{{< readfile file = "content/reusable/md/recipes_yaml_json_overview.md" >}} + +For information about Ruby recipes, see the [Ruby recipe documentation]({{< relref "recipes" >}}). + +## Create a JSON or YAML recipe + +To create a JSON or YAML recipe, follow these steps: + +1. Create a JSON or YAML file for your recipe in the same locations as Ruby recipes: + + - Standard recipe location: + + - `cookbooks/cookbook_name/recipes/default.yml` + - `cookbooks/cookbook_name/recipes/default.yaml` + - `cookbooks/cookbook_name/recipes/default.json` + + - Named recipes: + + - `cookbooks/cookbook_name/recipes/web.yml` + - `cookbooks/cookbook_name/recipes/database.yaml` + - `cookbooks/cookbook_name/recipes/app.json` + + - Root-level recipe alias (acts as the default recipe): + + - `cookbooks/cookbook_name/recipe.yml` + - `cookbooks/cookbook_name/recipe.yaml` + - `cookbooks/cookbook_name/recipe.json` + + {{< note >}} + + Creating more than one recipe with the same filename but different file extensions isn't supported. For example, `default.yaml` and `default.yml`. + + {{< /note >}} + +1. Define your recipe with the top-level `resources` key containing an array of items where each item has the following: + + - `type`: The Chef resource type (string) + - `name`: The resource name/identifier (string) + - resource-specific actions and properties as key-value pairs + + For example: + + {{< foundation_tabs tabs-id="create-json-yaml-recipe-example" >}} + {{< foundation_tab active="true" panel-link="create-yaml-recipe-example" tab-text="YAML">}} + {{< foundation_tab panel-link="create-json-recipe-example" tab-text="JSON" >}} + {{< /foundation_tabs >}} + + {{< foundation_tabs_panels tabs-id="create-json-yaml-recipe-example" >}} + {{< foundation_tabs_panel active="true" panel-id="create-yaml-recipe-example" >}} + + ```yaml + resources: + - type: "package" + name: "nginx" + action: "install" + version: "1.18.0" + - type: "service" + name: "nginx" + action: ["enable", "start"] + ``` + + {{< /foundation_tabs_panel >}} + + {{< foundation_tabs_panel panel-id="create-json-recipe-example" >}} + + ```json + { + "resources": [ + { + "type": "package", + "name": "nginx", + "action": "install", + "version": "1.18.0" + }, + { + "type": "service", + "name": "nginx", + "action": [ + "enable", + "start" + ] + } + ] + } + ``` + + {{< /foundation_tabs_panel >}} + {{< /foundation_tabs_panels >}} + + In this example: + + - the [`package` resource]({{< relref "/resources/package/" >}}) uses the `install` action and the `version` property to install Nginx 1.18.0. + - the [`service` resource]({{< relref "/resources/service/" >}}) uses the `enable` and `start` actions to enable and start Nginx. + +## Examples + +### Basic file management + +Use the [`directory` resource]({{< relref "/resources/directory">}}) to create the `/opt/app_name` directory and apply owner and group permissions to the directory. Use the [`file` resource]({{< relref "/resources/">}}) to create the `/opt/app_name/config.txt` file, add text to the file, and apply file owner and group permissions to the file. + +{{< foundation_tabs tabs-id="basic-file-management-json-yaml-recipe-example" >}} + {{< foundation_tab active="true" panel-link="basic-file-management-yaml-recipe-example" tab-text="YAML">}} + {{< foundation_tab panel-link="basic-file-management-json-recipe-example" tab-text="JSON" >}} +{{< /foundation_tabs >}} + +{{< foundation_tabs_panels tabs-id="basic-file-management-json-yaml-recipe-example" >}} +{{< foundation_tabs_panel active="true" panel-id="basic-file-management-yaml-recipe-example" >}} + +```yaml +--- +resources: + - type: "directory" + name: "/opt/app_name" + owner: "app_name" + group: "app_name" + mode: "0755" + recursive: true + + - type: "file" + name: "/opt/app_name/config.txt" + content: "This is a configuration file" + owner: "app_name" + group: "app_name" + mode: "0644" +``` + +{{< /foundation_tabs_panel >}} + +{{< foundation_tabs_panel panel-id="basic-file-management-json-recipe-example" >}} + +```json +{ + "resources": [ + { + "type": "directory", + "name": "/opt/app_name", + "owner": "app_name", + "group": "app_name", + "mode": "0755", + "recursive": true + }, + { + "type": "file", + "name": "/opt/app_name/config.txt", + "content": "This is a configuration file", + "owner": "app_name", + "group": "app_name", + "mode": "0644" + } + ] +} +``` + +{{< /foundation_tabs_panel >}} +{{< /foundation_tabs_panels >}} + +### Package and service management + +Use the [`package` resource]({{< relref "/resources/package">}}) to install Nginx and curl. Then use the [`service` resource]({{< relref "/resources/service">}}) to enable and start Nginx. + +{{< foundation_tabs tabs-id="package-service-management-json-yaml-recipe-example" >}} + {{< foundation_tab active="true" panel-link="package-service-management-yaml-recipe-example" tab-text="YAML">}} + {{< foundation_tab panel-link="package-service-management-json-recipe-example" tab-text="JSON" >}} +{{< /foundation_tabs >}} + +{{< foundation_tabs_panels tabs-id="package-service-management-json-yaml-recipe-example" >}} +{{< foundation_tabs_panel active="true" panel-id="package-service-management-yaml-recipe-example" >}} + +```yaml +--- +resources: + - type: "package" + name: "nginx" + action: "install" + + - type: "package" + name: "curl" + action: "install" + + - type: "service" + name: "nginx" + action: ["enable", "start"] +``` + +{{< /foundation_tabs_panel >}} + +{{< foundation_tabs_panel panel-id="package-service-management-json-recipe-example" >}} + +```json +{ + "resources": [ + { + "type": "package", + "name": "nginx", + "action": "install" + }, + { + "type": "package", + "name": "curl", + "action": "install" + }, + { + "type": "service", + "name": "nginx", + "action": [ + "enable", + "start" + ] + } + ] +} +``` + +{{< /foundation_tabs_panel >}} +{{< /foundation_tabs_panels >}} + +### User management + +Use the [`group` resource]({{< relref "/resources/group">}}) to create a group called "developers" and the [`user` resource]({{< relref "/resources/">}}) to create a user, give them properties, and assign them to the developers group. + +{{< foundation_tabs tabs-id="user-management-json-yaml-recipe-example" >}} + {{< foundation_tab active="true" panel-link="user-management-yaml-recipe-example" tab-text="YAML">}} + {{< foundation_tab panel-link="user-management-json-recipe-example" tab-text="JSON" >}} +{{< /foundation_tabs >}} + +{{< foundation_tabs_panels tabs-id="user-management-json-yaml-recipe-example" >}} +{{< foundation_tabs_panel active="true" panel-id="user-management-yaml-recipe-example" >}} + +```yaml +--- +resources: + - type: "group" + name: "developers" + gid: 3000 + + - type: "user" + name: "alice" + uid: 2001 + gid: 3000 + home: "/home/alice" + shell: "/bin/bash" + action: "create" +``` + +{{< /foundation_tabs_panel >}} + +{{< foundation_tabs_panel panel-id="user-management-json-recipe-example" >}} + +```json +{ + "resources": [ + { + "type": "group", + "name": "developers", + "gid": 3000 + }, + { + "type": "user", + "name": "alice", + "uid": 2001, + "gid": 3000, + "home": "/home/alice", + "shell": "/bin/bash", + "action": "create" + } + ] +} +``` + +{{< /foundation_tabs_panel >}} +{{< /foundation_tabs_panels >}} + +### Template with static variables + +Use the [`template` resource]({{< relref "/resources/template">}}) create the `/etc/app_name/config.yml` file using the `config.yml.erb` template. + +{{< foundation_tabs tabs-id="template-with-static-variables-json-yaml-recipe-example" >}} + {{< foundation_tab active="true" panel-link="template-with-static-variables-yaml-recipe-example" tab-text="YAML">}} + {{< foundation_tab panel-link="template-with-static-variables-json-recipe-example" tab-text="JSON" >}} +{{< /foundation_tabs >}} + +{{< foundation_tabs_panels tabs-id="template-with-static-variables-json-yaml-recipe-example" >}} +{{< foundation_tabs_panel active="true" panel-id="template-with-static-variables-yaml-recipe-example" >}} + +```yaml +--- +resources: + - type: "template" + name: "/etc/app_name/config.yml" + source: "config.yml.erb" + owner: "root" + group: "root" + mode: "0644" +``` + +{{< /foundation_tabs_panel >}} + +{{< foundation_tabs_panel panel-id="template-with-static-variables-json-recipe-example" >}} + +```json +{ + "resources": [ + { + "type": "template", + "name": "/etc/app_name/config.yml", + "source": "config.yml.erb", + "owner": "root", + "group": "root", + "mode": "0644" + } + ] +} +``` + +{{< /foundation_tabs_panel >}} +{{< /foundation_tabs_panels >}} + +### Guards + +Some common resource functionality is also supported, as long as the value of a property can be expressed as one of the four primitive types (string, integer, boolean, array). That means it's possible to use [`only_if` or `not_if` guards]({{< relref "/resource_common#guards" >}}) as long as they shell out to Bash or PowerShell and aren't passed a Ruby block. + +For example, this is supported: + +{{< foundation_tabs tabs-id="guards-json-yaml-recipe-example" >}} + {{< foundation_tab active="true" panel-link="guards-yaml-recipe-example" tab-text="YAML">}} + {{< foundation_tab panel-link="guards-json-recipe-example" tab-text="JSON" >}} +{{< /foundation_tabs >}} + +{{< foundation_tabs_panels tabs-id="guards-json-yaml-recipe-example" >}} +{{< foundation_tabs_panel active="true" panel-id="guards-yaml-recipe-example" >}} + +```yaml +resources: +- type: "directory" + name: "/var/www/html" + only_if: "which apache2" +``` + +{{< /foundation_tabs_panel >}} + +{{< foundation_tabs_panel panel-id="guards-json-recipe-example" >}} + +```json +{ + "resources": [ + { + "type": "directory", + "name": "/var/www/html", + "only_if": "which apache2" + } + ] +} +``` + +{{< /foundation_tabs_panel >}} +{{< /foundation_tabs_panels >}} + +Ruby blocks aren't supported: + +```yaml +resources: +- type: "directory" + name: "/var/www/html" + only_if: "{ ::File.exist?('/usr/sbin/apache2') }" +``` + +## Convert a JSON or YAML recipe to Ruby + +Use the `knife yaml convert` command to convert YAML recipes to Ruby: + +```shell +knife yaml convert recipes/default.yml recipes/default.rb +``` + +Converting from Ruby to YAML or JSON isn't supported due to their limitations. + +## YAML and JSON recipe limitations + +Chef Infra YAML and JSON recipes have the following limitation. + +### No Ruby code blocks + +YAML and JSON recipes can't include Ruby code blocks, which limits their functionality compared to Ruby recipes: + +```ruby +# ❌ Cannot be expressed in YAML - Ruby blocks not supported +template "/etc/nginx/nginx.conf" do + source "nginx.conf.erb" + variables({ + worker_processes: node['cpu']['total'] + }) + notifies :restart, "service[nginx]", :delayed + only_if { node['platform'] == 'ubuntu' } +end +``` + +### No conditional logic + +YAML and JSON recipes can't include conditional logic like `if`, `unless`, `only_if`, or `not_if` with Ruby expressions: + +```yaml +# ❌ Cannot include complex conditionals +resources: + - type: "package" + name: "nginx" + # Cannot do: only_if { node['platform'] == 'ubuntu' } +``` + +### No node attribute access + +YAML and JSON recipes can't directly access node attributes or perform Ruby evaluations: + +```yaml +# ❌ Cannot access node attributes dynamically +resources: + - type: "user" + name: "webapp" + # Cannot do: home "/home/#{node['webapp']['user']}" + home: "/home/webapp" # Must be static +``` + +### No resource notifications + +YAML and JSON recipes can't express complex resource relationships and notifications: + +```yaml +# ❌ Cannot express notifications between resources +resources: + - type: "template" + name: "/etc/nginx/nginx.conf" + source: "nginx.conf.erb" + # Cannot do: notifies :restart, "service[nginx]", :delayed +``` + +### No include or require functionality + +YAML and JSON recipes can't include other recipes or libraries: + +```yaml +# ❌ Cannot include other recipes +# include_recipe "cookbook::other_recipe" +``` + +## Troubleshooting + +### Missing `resources` key + +Chef Infra Client returns this error if a recipe is missing the top-level `resources` hash. + +```text +ArgumentError: YAML recipe 'recipes/default.yml' must contain a top-level 'resources' hash (YAML sequence), i.e. 'resources:' +``` + +### Single document limitation + +YAML recipes support only one YAML document in each file. Multiple documents separated by `---` aren't allowed: + +```yaml +--- +resources: + - type: "file" + name: "/tmp/file1.txt" +--- +resources: + - type: "file" + name: "/tmp/file2.txt" +``` + +Chef Infra Client returns the following error with multiple documents in one file: + +```text +ArgumentError: YAML recipe 'recipes/default.yml' contains multiple documents, only one is supported +``` + +### Ambiguous file extensions + +Chef Infra Client returns this error if two recipes have the same filename with different file extensions. For example, `default.yaml` and `default.yml`. + +```text +Chef::Exceptions::AmbiguousYAMLFile: Found both default.yml and default.yaml in cookbook, update the cookbook to remove one +``` diff --git a/content/recipes_yaml.md b/content/recipes_yaml.md deleted file mode 100644 index 7f79d53a3b..0000000000 --- a/content/recipes_yaml.md +++ /dev/null @@ -1,277 +0,0 @@ -+++ -title = "About YAML recipes" -draft = false -gh_repo = "chef-web-docs" - -[menu] - [menu.infra] - title = "YAML recipes" - identifier = "chef_infra/cookbook_reference/recipes/YAML recipes" - parent = "chef_infra/cookbook_reference/recipes" - weight = 20 -+++ - -YAML recipes let you define Chef Infra resources using YAML syntax instead of Ruby. This feature makes Chef Infra recipes more accessible to users who prefer declarative YAML syntax over Ruby code. - -YAML recipes simplify defining Chef resources for basic use cases. While they have significant limitations compared to Ruby recipes, they're valuable for teams that prefer YAML syntax and don't need advanced Chef DSL features. For complex scenarios involving dynamic logic, node attributes, or resource relationships, use Ruby recipes. - -For most production environments, use a hybrid approach: YAML recipes for simple static configurations and Ruby recipes for complex logic. This approach balances simplicity and functionality. - -## Create a YAML recipe - -To create a YAML recipe, follow these steps: - -1. Create a YAML file for your recipe in the same locations as Ruby recipes: - - - Standard recipe location: - - `cookbooks/cookbook_name/recipes/default.yml` - - `cookbooks/cookbook_name/recipes/default.yaml` - - Named recipes: - - `cookbooks/cookbook_name/recipes/web.yml` - - `cookbooks/cookbook_name/recipes/database.yaml` - - Root-level recipe alias (acts as the default recipe): - - `cookbooks/cookbook_name/recipe.yml` - - `cookbooks/cookbook_name/recipe.yaml` - - Chef Infra supports YAML recipes with both `.yml` and `.yaml` extensions. - -1. Define your recipe with a top-level `resources` key containing an array of resources where each item has the following: - - - `type`: The Chef resource type (string) - - `name`: The resource name/identifier (string) - - resource-specific actions and properties as key-value pairs - - For example: - - ```yaml - resources: - - type: "package" - name: "nginx" - action: "install" - version: "1.18.0" - - type: "service" - name: "nginx" - action: ["enable", "start"] - ``` - - In this example: - - - the [`package` resource]({{< relref "/resources/package/" >}}) uses the `install` action and the `version` property to install Nginx 1.18.0. - - the [`service` resource]({{< relref "/resources/service/" >}}) uses the `enable` and `start` actions to enable and start Nginx. - -## Examples - -### Basic file management - -Use the [`directory` resource]({{< relref "/resources/directory">}}) to create the `/opt/app_name` directory and apply owner and group permissions to the directory. Use the [`file` resource]({{< relref "/resources/">}}) to create the `/opt/app_name/config.txt` file, add text to the file, and apply file owner and group permissions to the file. - -```yaml ---- -resources: - - type: "directory" - name: "/opt/app_name" - owner: "app_name" - group: "app_name" - mode: "0755" - recursive: true - - - type: "file" - name: "/opt/app_name/config.txt" - content: "This is a configuration file" - owner: "app_name" - group: "app_name" - mode: "0644" -``` - -### Package and service management - -Use the [`package` resource]({{< relref "/resources/package">}}) to install Nginx and curl. Then use the [`service` resource]({{< relref "/resources/service">}}) to enable and start Nginx. - -```yaml ---- -resources: - - type: "package" - name: "nginx" - action: "install" - - - type: "package" - name: "curl" - action: "install" - - - type: "service" - name: "nginx" - action: ["enable", "start"] -``` - -### User management - -Use the [`group` resource]({{< relref "/resources/group">}}) to create a group called "developers" and the [`user` resource]({{< relref "/resources/">}}) to create a user, give them properties, and assign them to the developers group. - -```yaml ---- -resources: - - type: "group" - name: "developers" - gid: 3000 - - - type: "user" - name: "alice" - uid: 2001 - gid: 3000 - home: "/home/alice" - shell: "/bin/bash" - action: "create" -``` - -### Template with static variables - -Use the [`template` resource]({{< relref "/resources/template">}}) create the `/etc/app_name/config.yml` file using the `config.yml.erb` template. - -```yaml ---- -resources: - - type: "template" - name: "/etc/app_name/config.yml" - source: "config.yml.erb" - owner: "root" - group: "root" - mode: "0644" -``` - -### Guards - -Some common resource functionality is also supported, as long as the value of a property can be expressed as one of the four primitive types (string, integer, boolean, array). That means it's possible to use [`only_if` or `not_if` guards]({{< relref "/resource_common#guards" >}}) as long as they shell out to Bash or PowerShell and aren't passed a Ruby block. - -For example, this is supported: - -```yaml -resources: -- type: "directory" - name: "/var/www/html" - only_if: "which apache2" -``` - -Ruby blocks aren't supported: - -```yaml -resources: -- type: "directory" - name: "/var/www/html" - only_if: "{ ::File.exist?('/usr/sbin/apache2') }" -``` - -## Convert a YAML recipe to Ruby - -Use the `knife yaml convert` command to convert YAML recipes to Ruby: - -```shell -knife yaml convert recipes/default.yml recipes/default.rb -``` - -Converting from Ruby to YAML isn't supported due to YAML's limitations. - -## YAML recipe limitations - -Chef Infra YAML recipes have the following limitation. - -### No Ruby code blocks - -YAML recipes can't include Ruby code blocks, which limits their functionality compared to Ruby recipes: - -```ruby -# ❌ Cannot be expressed in YAML - Ruby blocks not supported -template "/etc/nginx/nginx.conf" do - source "nginx.conf.erb" - variables({ - worker_processes: node['cpu']['total'] - }) - notifies :restart, "service[nginx]", :delayed - only_if { node['platform'] == 'ubuntu' } -end -``` - -### No conditional logic - -YAML recipes can't include conditional logic like `if`, `unless`, `only_if`, or `not_if` with Ruby expressions: - -```yaml -# ❌ Cannot include complex conditionals -resources: - - type: "package" - name: "nginx" - # Cannot do: only_if { node['platform'] == 'ubuntu' } -``` - -### No node attribute access - -YAML recipes can't directly access node attributes or perform Ruby evaluations: - -```yaml -# ❌ Cannot access node attributes dynamically -resources: - - type: "user" - name: "webapp" - # Cannot do: home "/home/#{node['webapp']['user']}" - home: "/home/webapp" # Must be static -``` - -### No resource notifications - -YAML recipes can't express complex resource relationships and notifications: - -```yaml -# ❌ Cannot express notifications between resources -resources: - - type: "template" - name: "/etc/nginx/nginx.conf" - source: "nginx.conf.erb" - # Cannot do: notifies :restart, "service[nginx]", :delayed -``` - -### No include or require functionality - -YAML recipes can't include other recipes or libraries: - -```yaml -# ❌ Cannot include other recipes -# include_recipe "cookbook::other_recipe" -``` - -## Troubleshooting - -### Missing `resources` key - -Chef Infra Client returns this error if a recipe is missing the top-level `resources` hash. - -```text -ArgumentError: YAML recipe 'recipes/default.yml' must contain a top-level 'resources' hash (YAML sequence), i.e. 'resources:' -``` - -### Single document limitation - -YAML recipes support only one YAML document in each file. Multiple documents separated by `---` aren't allowed: - -```yaml ---- -resources: - - type: "file" - name: "/tmp/file1.txt" ---- -resources: - - type: "file" - name: "/tmp/file2.txt" -``` - -Chef Infra Client returns the following error with multiple documents in one file: - -```text -ArgumentError: YAML recipe 'recipes/default.yml' contains multiple documents, only one is supported -``` - -### Ambiguous file extensions - -Chef Infra Client returns this error if two recipes have the same filename with different file extensions. For example, `default.yaml` and `default.yml`. - -```text -Chef::Exceptions::AmbiguousYAMLFile: Found both default.yml and default.yaml in cookbook, update the cookbook to remove one -``` diff --git a/content/reusable/md/recipes_yaml_json_overview.md b/content/reusable/md/recipes_yaml_json_overview.md new file mode 100644 index 0000000000..a6f78fd837 --- /dev/null +++ b/content/reusable/md/recipes_yaml_json_overview.md @@ -0,0 +1,5 @@ +JSON and YAML recipes let you define Chef Infra resources using a no-code syntax instead of Ruby. This feature makes Chef Infra recipes more accessible to users who prefer declarative YAML or JSON syntax over Ruby code. + +YAML and JSON recipes simplify defining Chef resources for basic use cases. While they have significant limitations compared to Ruby recipes, they're valuable for teams that prefer YAML syntax and don't need advanced Chef DSL features. For complex scenarios involving dynamic logic, node attributes, or resource relationships, use Ruby recipes. + +For most production environments, use a hybrid approach: YAML or JSON recipes for simple static configurations and Ruby recipes for complex logic. This approach balances simplicity and functionality. From ab89b510fd127257908faeafd7f215582e625b0e Mon Sep 17 00:00:00 2001 From: Ian Maddaus Date: Thu, 21 Aug 2025 19:31:27 -0400 Subject: [PATCH 08/10] One more change Signed-off-by: Ian Maddaus --- content/recipes_json_yaml.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/recipes_json_yaml.md b/content/recipes_json_yaml.md index 64d24b5acf..671ee52a67 100644 --- a/content/recipes_json_yaml.md +++ b/content/recipes_json_yaml.md @@ -371,13 +371,14 @@ resources: Ruby blocks aren't supported: ```yaml +# ❌ Cannot be expressed in YAML - Ruby blocks not supported resources: - type: "directory" name: "/var/www/html" only_if: "{ ::File.exist?('/usr/sbin/apache2') }" ``` -## Convert a JSON or YAML recipe to Ruby +## Convert a YAML recipe to Ruby Use the `knife yaml convert` command to convert YAML recipes to Ruby: From b947c02fc44230fd24ff3abad3bc54b722bd6732 Mon Sep 17 00:00:00 2001 From: Ian Maddaus Date: Fri, 22 Aug 2025 10:23:40 -0400 Subject: [PATCH 09/10] Fix linting issues Signed-off-by: Ian Maddaus --- content/recipes_json_yaml.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/content/recipes_json_yaml.md b/content/recipes_json_yaml.md index 671ee52a67..0a113d7543 100644 --- a/content/recipes_json_yaml.md +++ b/content/recipes_json_yaml.md @@ -371,7 +371,7 @@ resources: Ruby blocks aren't supported: ```yaml -# ❌ Cannot be expressed in YAML - Ruby blocks not supported +# Can't be expressed in YAML - Ruby blocks not supported resources: - type: "directory" name: "/var/www/html" @@ -390,14 +390,14 @@ Converting from Ruby to YAML or JSON isn't supported due to their limitations. ## YAML and JSON recipe limitations -Chef Infra YAML and JSON recipes have the following limitation. +Chef Infra YAML and JSON recipes have the following limitations. ### No Ruby code blocks YAML and JSON recipes can't include Ruby code blocks, which limits their functionality compared to Ruby recipes: ```ruby -# ❌ Cannot be expressed in YAML - Ruby blocks not supported +# Can't be expressed in YAML - Ruby blocks not supported template "/etc/nginx/nginx.conf" do source "nginx.conf.erb" variables({ @@ -413,11 +413,11 @@ end YAML and JSON recipes can't include conditional logic like `if`, `unless`, `only_if`, or `not_if` with Ruby expressions: ```yaml -# ❌ Cannot include complex conditionals +# Can't include complex conditionals resources: - type: "package" name: "nginx" - # Cannot do: only_if { node['platform'] == 'ubuntu' } + # Can't do: only_if { node['platform'] == 'ubuntu' } ``` ### No node attribute access @@ -425,11 +425,11 @@ resources: YAML and JSON recipes can't directly access node attributes or perform Ruby evaluations: ```yaml -# ❌ Cannot access node attributes dynamically +# Can't access node attributes dynamically resources: - type: "user" name: "webapp" - # Cannot do: home "/home/#{node['webapp']['user']}" + # Can't do: home "/home/#{node['webapp']['user']}" home: "/home/webapp" # Must be static ``` @@ -438,12 +438,12 @@ resources: YAML and JSON recipes can't express complex resource relationships and notifications: ```yaml -# ❌ Cannot express notifications between resources +# Can't express notifications between resources resources: - type: "template" name: "/etc/nginx/nginx.conf" source: "nginx.conf.erb" - # Cannot do: notifies :restart, "service[nginx]", :delayed + # Can't do: notifies :restart, "service[nginx]", :delayed ``` ### No include or require functionality @@ -451,7 +451,7 @@ resources: YAML and JSON recipes can't include other recipes or libraries: ```yaml -# ❌ Cannot include other recipes +# Can't include other recipes # include_recipe "cookbook::other_recipe" ``` From f498b64270f194261688d327d61271e40902fd5c Mon Sep 17 00:00:00 2001 From: Ian Maddaus Date: Fri, 22 Aug 2025 13:54:58 -0400 Subject: [PATCH 10/10] Add recipe support versions Signed-off-by: Ian Maddaus --- content/recipes_json_yaml.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/recipes_json_yaml.md b/content/recipes_json_yaml.md index 0a113d7543..97e1d36c35 100644 --- a/content/recipes_json_yaml.md +++ b/content/recipes_json_yaml.md @@ -15,6 +15,10 @@ gh_repo = "chef-web-docs" For information about Ruby recipes, see the [Ruby recipe documentation]({{< relref "recipes" >}}). +## Support + +We introduced YAML recipes in Chef Infra Client 16.0. We added support for YAML recipes with the `.yml` file extension in Infra Client 17.2.29. We added support for JSON recipes in Chef Infra Client 18.8. + ## Create a JSON or YAML recipe To create a JSON or YAML recipe, follow these steps: