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.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..97e1d36c35 --- /dev/null +++ b/content/recipes_json_yaml.md @@ -0,0 +1,499 @@ ++++ +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" >}}). + +## 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: + +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 +# Can't be expressed in YAML - Ruby blocks not supported +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 or JSON isn't supported due to their limitations. + +## YAML and JSON recipe limitations + +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 +# Can't 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 +# Can't include complex conditionals +resources: + - type: "package" + name: "nginx" + # Can't 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 +# Can't access node attributes dynamically +resources: + - type: "user" + name: "webapp" + # Can't 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 +# Can't express notifications between resources +resources: + - type: "template" + name: "/etc/nginx/nginx.conf" + source: "nginx.conf.erb" + # Can't do: notifies :restart, "service[nginx]", :delayed +``` + +### No include or require functionality + +YAML and JSON recipes can't include other recipes or libraries: + +```yaml +# Can't 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. 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