class Chef::ChefFS::ChefFSDataStore

Translation layer between chef-zero’s DataStore (a place where it expects files to be stored) and ChefFS (the user’s repository directory layout).

chef-zero expects the data store to store files its way–for example, it expects get(“nodes/blah”) to return the JSON text for the blah node, and it expects get(“cookbooks/blah/1.0.0”) to return the JSON definition of the blah cookbook version 1.0.0.

The repository is defined the way the user wants their layout. These two things are very similar in layout (for example, nodes are stored under the nodes/ directory and their filename is the name of the node).

However, there are a few differences that make this more than just a raw file store:

  1. Cookbooks are stored much differently.

- chef-zero places JSON text with the checksums for the cookbook at
  /cookbooks/NAME/VERSION, and expects the JSON to contain URLs to the
  actual files, which are stored elsewhere.
- The repository contains an actual directory with just the cookbook
  files and a metadata.rb containing a version #.  There is no JSON to
  be found.
- Further, if versioned_cookbooks is false, that directory is named
  /cookbooks/NAME and only one version exists.  If versioned_cookbooks
  is true, the directory is named /cookbooks/NAME-VERSION.
- Therefore, ChefFSDataStore calculates the cookbook JSON by looking at
  the files in the cookbook and checksumming them, and reading metadata.rb
  for the version and dependency information.
- ChefFSDataStore also modifies the cookbook file URLs so that they point
  to /file_store/repo/<filename> (the path to the actual file under the
  repository root).  For example, /file_store/repo/apache2/metadata.rb or
  /file_store/repo/cookbooks/apache2/recipes/default.rb).
  1. Sandboxes don’t exist in the repository.

- ChefFSDataStore lets cookbooks be uploaded into a temporary memory
  storage, and when the cookbook is committed, copies the files onto the
  disk in the correct place (/cookbooks/apache2/recipes/default.rb).
  1. Data bags:

- The Chef server expects data bags in /data/BAG/ITEM
- The repository stores data bags in /data_bags/BAG/ITEM
  1. JSON filenames are generally NAME.json in the repository (e.g. /nodes/foo.json).

  2. Org membership: chef-zero stores user membership in an org as a series of empty files. If an org has jkeiser and cdoherty as members, chef-zero expects these files to exist:

    • ‘users/jkeiser` (content: ’{}‘)

    • ‘users/cdoherty` (content: ’{}‘)

    ChefFS, on the other hand, stores user membership in an org as a single file, ‘members.json`, with content:

    ```json
    [
      { "user": { "username": "jkeiser" } },
      { "user": { "username": "cdoherty" } }
    ]
    ```

    To translate between the two, we need to intercept requests to ‘users` like so:

    • ‘list(users)` -> `get(/members.json)`

    • ‘get(users/NAME)` -> `get(/members.json)`, see if it’s in there

    • ‘create(users/NAME)` -> `get(/members.json)`, add name, `set(/members.json)`

    • ‘delete(users/NAME)` -> `get(/members.json)`, remove name, `set(/members.json)`

  3. Org invitations: chef-zero stores org membership invitations as a series of empty files. If an org has invited jkeiser and cdoherty (and they have not yet accepted the invite), chef-zero expects these files to exist:

    • ‘association_requests/jkeiser` (content: ’{}‘)

    • ‘association_requests/cdoherty` (content: ’{}‘)

    ChefFS, on the other hand, stores invitations as a single file, ‘invitations.json`, with content:

    ```json
    [
      { "id" => "jkeiser-chef", 'username' => 'jkeiser' },
      { "id" => "cdoherty-chef", 'username' => 'cdoherty' }
    ]
    ```

    To translate between the two, we need to intercept requests to ‘users` like so:

    • ‘list(association_requests)` -> `get(/invitations.json)`

    • ‘get(association_requests/NAME)` -> `get(/invitations.json)`, see if it’s in there

    • ‘create(association_requests/NAME)` -> `get(/invitations.json)`, add name, `set(/invitations.json)`

    • ‘delete(association_requests/NAME)` -> `get(/invitations.json)`, remove name, `set(/invitations.json)`