{"id":13685600,"url":"https://github.com/facebook/chef-cookbooks","last_synced_at":"2025-05-15T01:07:53.130Z","repository":{"id":42653737,"uuid":"48260686","full_name":"facebook/chef-cookbooks","owner":"facebook","description":"Open source chef cookbooks.","archived":false,"fork":false,"pushed_at":"2025-05-09T22:06:49.000Z","size":2810,"stargazers_count":577,"open_issues_count":17,"forks_count":145,"subscribers_count":62,"default_branch":"main","last_synced_at":"2025-05-09T23:20:10.971Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/facebook.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2015-12-18T23:14:27.000Z","updated_at":"2025-05-09T22:06:53.000Z","dependencies_parsed_at":"2024-05-03T17:27:59.454Z","dependency_job_id":"e0fbcb98-3ad1-424b-ad00-cca8f066a385","html_url":"https://github.com/facebook/chef-cookbooks","commit_stats":{"total_commits":1485,"total_committers":243,"mean_commits":6.111111111111111,"dds":0.8107744107744108,"last_synced_commit":"68d7b7736dca858fc7ba1960e53cf7a1070f82a2"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/facebook%2Fchef-cookbooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/facebook%2Fchef-cookbooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/facebook%2Fchef-cookbooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/facebook%2Fchef-cookbooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/facebook","download_url":"https://codeload.github.com/facebook/chef-cookbooks/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254254041,"owners_count":22039792,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T14:00:54.377Z","updated_at":"2025-05-15T01:07:48.122Z","avatar_url":"https://github.com/facebook.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Facebook Cookbooks Suite\n\n[![Continuous Integration](https://github.com/facebook/chef-cookbooks/actions/workflows/ci.yml/badge.svg)](https://github.com/facebook/chef-cookbooks/actions/workflows/ci.yml)\n\nThis repo contains attribute-driven-API cookbooks maintained by Facebook. It's\na large chunk of what we refer to as our \"core cookbooks.\"\n\nIt's worth reading our\n[Philosophy.md](https://github.com/facebook/chef-utils/blob/main/Philosophy.md)\ndoc before reading this. It covers how we use Chef and is important context\nbefore reading this.\n\nIt is important to note that these cookbooks are built using a very specific\nmodel that's very different from most other cookbooks in the community.\nSpecifically:\n\n* It assumes an environment in which you want to delegate. The cookbooks are\n  designed to be organized in a \"least specific to most specific\" order in the\n  run-list. The run-list starts with the \"core cookbooks\" that setup APIs and\n  enforce a base standard, which can be adjusted by the service owners using\n  cookbooks later in the run-list.\n* It assumes a \"run from main\" type environment. At Facebook we use [Grocery\n  Delivery](http://www.github.com/facebook/grocery-delivery) to sync the main\n  branch of our source control repo with all of our Chef servers. Grocery\n  Delivery is not necessary to use these cookbooks, but since they were built\n  with this model in mind, the versions never change (relatedly: we do not use\n  environments).\n* It assumes you have a testing toolset that allows anyone modifying later\n  cookbooks to ensure that their use of the API worked as expected on a live\n  node before committing. For this, we use [Taste\n  Tester](http://www.github.com/facebook/taste-tester).\n\nCookbooks in this repo all begin with `fb_` to denote that not only do they\nuse the Facebook Cookbook Model, but that they are maintained in this repo.\n\nLocal cookbooks or cookbooks in other repositories that implement this model\nshould not use this prefix, but should reference this document in their docs.\n\n\n## APIs\n\nUnlike other cookbook models, we do not use resources as APIs, we use the node\nobject. Configuration is modeled in arrays and hashes as closely and thinly as\npossible to the service we are configuring. Ideally, you should only have to\nread the docs to the service to configure it, not the docs to the cookbook.\n\nFor example, if the service we are configuring has a key-value pair\nconfiguration file, we will provide a simple hash where keys and values will be\ndirectly put into the necessary configuration file.\n\nThere are two reasons we use attribute-driven APIs:\n\n1. Cascading configuration\n   Since our cookbooks are ordered least specific (core team that owns Chef) to\n   most specific (the team that owns this machine or service) it means that the\n   team who cares about this specific instance can always override anything.\n   This enables stacking that is not possible in many other models. For\n   example, you can have a run-list that looks like:\n\n   * Core cookbooks (the ones in this repo)\n   * Site/Company cookbooks (site-specific settings)\n   * Region cookbooks (overrides for a given region/cluster)\n   * Application Category cookbooks (webserver, mail server, etc.)\n   * Specific Application cookbook (\"internal app1 server\")\n\n   So let's say that you want a specific amount of shared memory by default,\n   but in some region you know you have different size machines, so you shrink\n   it, but web servers need a further different setting, and then finally some\n   specific internal webserver needs an even more specific setting... this all\n   just works.\n\n   Further, a cookbook can see the value that was set before it modifies\n   things, so the 'webserver' cookbook could look to see what the value was\n   (small or large) before modifying it and adjust it accordingly (so it could\n   be relative to the size of memory that the 'region' cookbook set).\n\n   Using resources for this does not allow this \"cascading\", it instead creates\n   \"fighting\". If you use the cron resource to setup an hourly job, and then\n   someone else creates a cron for that same job but only twice a day, then\n   during each Chef run the cron job gets modified to hourly, then re-modified\n   to twice a day.\n\n2. Allows for what we refer to as \"idempotent systems\" instead of \"idempotent\n   settings.\" In other words, if you only manage a specific item in a larger\n   config, and then you stop managing it, it should either revert to a\n   less-specific setting (see #1) or be removed, as necessary.\n\n   For example let's say you want to set a cron job. If you use the internal\n   cron resource, and then delete the recipe code that adds that cronjob, that\n   cron isn't removed from your production environment - it's on all existing\n   nodes, but not on any new nodes.\n\n   For this reason we use templates to take over a whole configuration wherever\n   possible. All cron jobs in our `fb_cron` API are written to\n   `/etc/cron.d/fb_crontab`. If you delete the lines adding a cronjob, since\n   they are just entries in a hash, when the template is generated on the next\n   Chef run, those crons go away.\n\n   Alternatively, consider a sysctl set by the \"site\" cookbook, then\n   overwritten by a later cookbook. When that later code is removed, the entry\n   in the hash falls back to being set again by the next-most-specific value\n   (i.e. the \"site\" cookbook in this case).\n\n\n## Run-lists\n\nHow you formulate your run-lists is up to your site, as long as you follow the\nbasic rule that core cookbooks come first and you order least-specific to\nmost-specific. At Facebook, all of our run-lists are:\n\n    recipe[fb_init], recipe[$SERVICE]\n\nWhere `fb_init` is similar to the sample provided in this repo, but with extra\n\"core cookbooks.\"\n\nWe generally think of this way: `fb_init` should make you a \"Facebook server\"\nand the rest should make you a whatever-kind-of-server-you-are.\n\n\n## Getting started\n\nGrab a copy of the repo, rename `fb_init_sample` to `fb_init`, and follow the\ninstructions in its [README.md](https://github.com/facebook/chef-cookbooks/blob/main/cookbooks/fb_init_sample/README.md)\n(coordinating guidance is in comments in the default recipe).\n\n\n## Other Guidelines\n\n### Modules and classes in cookbooks\n\nIt is often useful to factor out logic into a library - especially logic that\ndoesn't create resources. Doing so makes this logic easier to unit test and\nmakes the recipe or resource cleaner.\n\nOur standard is that all cookbooks use the top-level container of `module FB`,\nand then create a class for their cookbook under that. For example, `fb_fstab`\ncreates a `class Fstab` inside of the `module FB`. We will refer to this as\nthe cookbook class from here.\n\nWe require all cookbooks use this model for consistency.\n\nSince we don't put anything other than other classes inside the top-level\nobject, it's clear that a `module` is the right choice.\n\nWhile there is no reason that a cookbook class can't be one designed to be\ninstantiated, more often than not it is simply a collection of class methods\nand constants (i.e. static data and methods that can then be called both from\nthis cookbook and others).\n\nBelow the cookbook class, the author is free to make whatever class or methods\nthey desire.\n\nWhen building a complicated Custom Resource, the recommended pattern is to\nfactor out the majority of the logic into a module, inside of the cookbook\nclass, that can be `include`d in the `action_class`. This allows the logic to\nbe easily unit tested using standard rspec. It is preferred for this module to\nbe in its own library file, and for its name to end in `Provider`, ala\n`FB::Thing::ThingProvider`.\n\nWhen more than 1 or 2 methods from this module are called from the custom\nresource itself, it is highly recommended you include it in a Helper class for\nclarity, ala:\n\n```\naction_class do\n  class ThingHelper\n    include FB::Thing::ThingProvider\n  end\nend\n```\n\nIn this way, it is clear where methods come from.\n\n### Extending the node vs self-contained classes\n\nYou may have noticed that some of our cookbooks will extend the `node` object,\nwhile others have self-contained classes that sometimes require the `node` be\npassed as a parameter to some methods.\n\nIn general, the **only** time when extending the `node` is acceptable is when\nyou are simply making a convenience function around using the node object. So,\nfor example, instead of making people do `node['platform_family'] = 'debian'`,\nthere's a `node.debian?`. This is simply syntactic sugar on top of data\nentirely in the node.\n\nIn all other cases, one should simply have the `node` be an argument passed on,\nso as to not pollute the node namespace. For example, a method that looks at\nthe node attributes, but also does a variety of other logic, should be in a\ncookbook class and take the node as an argument (per standard programming\nparadigms about clear dependencies).\n\n### Methods in recipes\n\nSometimes it is convenient to put a method directly in a recipe. It is strongly\npreferred to put these methods in the cookbook class, however there are some\ncases where methods directly in recipes make sense. The primary example is a\nsmall method which creates a resource based on some input to make a set of\nloops more readable.\n\n### Methods in templates\n\nMethods should not be put into templates. In general, as little logic as\npossible should be in templates. In general the easiest way to do this is to\nput the complex logic into methods in your cookbook class and call them from\nthe templates.\n\n### Err on the side of fail\n\nChef is an ordered system and thus is designed to fail a run if a resource\ncannot be converged. The reason for this is that if one step in an ordered\nlist cannot be completed, it's likely not safe to do at least some of the\nfollowing steps. For example, if you were not able to write the correct\nconfiguration for a service, then starting it may open up a security\nvulnerability.\n\nLikewise, the Facebook cookbooks will err on the side of failing if something\nseems wrong. This is both in line with the Chef philosophy we just outlined,\nbut also because this model assumes that code is being tested on real systems\nbefore being released using something like [taste-tester](https://github.com/facebook/taste-tester/) and that monitoring is\nin place to know if your machines are successfully running Chef.\n\nHere are some examples of this philosophy in practice:\n\n* If a cookbook is included on a platform it does not support, we `fail`. It\n  might seem like `return`ing in this case is reasonable but there is a good\n  indication the run-list isn't as-expected, so it's a great idea to bail out\n  before this machine is mis-configured.\n* If a configuration was passed in that we don't support, rather than ignore it\n  we `fail`.\n\n### Validation of inputs and `whyrun_safe_ruby_blocks`\n\nMany cookbooks rely on the service underneath and the testing of the user to be\nthe primary validator of inputs. Is the software we just configured, behaving\nas expected?\n\nHowever, sometimes it's useful to do our own validation because there are\ncertain configurations we don't want to support, because the software may\naccepted dangerous configurations we want to catch, or because the user could\npass us a combination of configurations that is conflicting or impossible to\nimplement.\n\nIn this model, however, this must be done at runtime. If your implementation is\ndone primarily inside of an internally-called resource, then this validation\ncan also be done there. However, if your implementation is primarily a recipe\nand templates, doing the validation in templates is obviously not desirable.\nThis is where `whyrun_safe_ruby_blocks` come in.\n\nUsing an ordinary `ruby_block` would suffice to have ruby code run at runtime\nto validate the attributes, however that means that the error would not be\ncaught in whyrun mode. Since this validation does not change the system, it is\nsafe to execute in whyrun mode, and that's why we use `whyrun_safe_ruby_block`s:\nthey are run in whyrun mode.\n\nIt is worth noting that this is also where you can take input that perhaps was\nin a structure convenient for users and build out a different data structure\nthat's more convenient to use in your template.\n\n### Implementing runtime-safe APIs\n\nThis model intentionally draws the complexity of Chef into the \"core cookbooks\"\n(those implementing APIs) so that the user experience of maintaining systems is\nsimple and (usually) requires little more than writing to the node object.\nHowever, the trade-off for that simplicity is that implementing the API\nproperly can be quite tricky.\n\nHow to do this is a large enough topic that it gets [its own document](https://github.com/facebook/chef-utils/blob/main/Compile-Time-Run-Time.md).\nHowever, some style guidance is also useful. This section assumes you have read\nthe aforementioned document.\n\nThe three main ways that runtime-safety is achieved are `lazy`, `templates`, and\n`custom resources`. When should you use which?\n\nThe template case is fairly straight forward - if you have a template, read the\nnode object from the within the template source instead of using `variables`\non the template resource, and all data read is inherently runtime safe since\ntemplates run at runtime.\n\nBut what about `lazy` vs `custom resources`? For example, in a recipe you might\ndo:\n\n```ruby\npackage 'thingy packages' do\n  package_name lazy {\n    pkgs = 'thingy'\n    if node['fb_thingy']['want_devel']\n      pkgs \u003c\u003c 'thingy-devel'\n    end\n    pkgs\n  }\n  action :upgrade\nend\n```\n\nWhere as inside of a custom resource you could instead do:\n\n```ruby\npkgs = 'thingy'\nif node['fb_thingy']['want_devel']\n  pkgs \u003c\u003c 'thingy-devel'\nend\n\npackage pkgs do\n  action :upgrade\nend\n```\n\nWhich one is better? There's not an exact answer, both work, so it's a style\nconsideration. In general, there are two times when we suggest a custom\nresource:\n\nThe first is when you need to loop over the node in order to even know what\nresources to create. Since this isn't possible to (well, technically it's\npossible with some ugliness, but by and large not using the standard DSL), this\nmust go into a custom resource. Example might be:\n\n```ruby\n# This MUST be inside of a custom resource!\nnode['fb_thingy']['instances'].each do |name, config|\n  template \"/etc/thingy/#{instance}.conf\" do\n    owner 'root'\n    group 'root'\n    mode '644'\n    variables({:config =\u003e config})\n  end\nend\n```\n\nThe second is when you're using `lazy` on the majority of the resources in\nyour recipe. If your recipe has 15 resources and you've had to pepper all of\nthem with `lazy`, it's a bit cleaner to make a custom resource that you call\nin your recipe.\n\nIt's important here to reiterate: we're **not** referring to using a Custom\nResource as an API, but simply making an internal custom resource, called\nonly by your own recipe, as a way to simplify runtime safety.\n\nOutside of these two cases, you should default to implementations inside of\nrecipes. This is for a few reasons.\n\nThe first reason is that dropping entire implementations in custom resources\nleads to confusion and sets a bad precedent for how runtime-safety works. For\nexample, consider the custom resource code we saw earlier where you assemble\nthe package list in \"naked\" ruby:\n\n```ruby\npkgs = 'thingy'\nif node['fb_thingy']['want_devel']\n  pkgs \u003c\u003c 'thingy-devel'\nend\n```\n\nThis code works fine in a resource, but serves as a bad reference for others -\nsince this absolutely won't work in a recipe (even though it'll run).\n\nThe second reason is that quite often implementations need both compile-time\nand runtime code, and by blindly dropping the implementation into a custom\nresource, you can often miss this and create bugs like this:\n\n```ruby\n# only safe because we're in a custom resource\npackages = FB::Thingy.determine_packages(node)\n\npackage packages do\n  action :upgrade\nend\n\nif node['fb_thingy']['want_cron']\n  node.default['fb_cron']['jobs']['thingy_runner'] = {\n    'time' =\u003e '* * * * *',\n    'command' =\u003e '/usr/bin/thingy --quiet',\n  }\nend\n\nservice 'thingy' do\n  action [:enable, :start]\nend\n```\n\nNote here that while this code all seems reasonable in a custom resource (`if`\nstatements are runtime safe when inside of a custom resource), that cronjob\nwill never get picked up, because you're using an API at runtime, but APIs must\nbe called at compile time and consumed at runtime. In reality, this needs to be\nin the recipe in order to work, and should look like this, in a *recipe*:\n\n```ruby\npackage 'thingy packages' do\n  package_name lazy { FB::Thingy.determine_packages(node) }\n  action :upgrade\nend\n\nnode.default['fb_cron']['jobs']['thingy_runner'] = {\n  'only_if' =\u003e proc { node['fb_thingy']['want_cron'] },\n  'time' =\u003e '* * * * *',\n  'command' =\u003e '/usr/bin/thingy --quiet',\n}\n\nservice 'thingy' do\n  action [:enable, :start]\nend\n```\n\nIn general, always start your implementation as a recipe and then escalate to\nCustom Resources where necessary.\n\n## Debugging kitchen runs\n\nYou can set up kitchen using the same commands as in `.github/workflows/ci.yml`,\nbut once Chef runs you won't have access to connect, so modify\n`fb_sudo/attributes/default.rb` and uncomment the kitchen block.\n\nThen you can do `bundle exec kitchen login \u003cINSTANCE\u003e` after a failed\nrun, and sudo will be passwordless so you can debug.\n\n## License\n\nSee the LICENSE file in this directory\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffacebook%2Fchef-cookbooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffacebook%2Fchef-cookbooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffacebook%2Fchef-cookbooks/lists"}