Batali is a light weight cookbook resolver.
Batali is a cookbook resolver. It's built to be light weight but feature rich. Batali helps to manage your cookbooks and stay out of your way.
Provide a Batali
file:
Batali.define do
source 'https://supermarket.chef.io'
cookbook 'postgresql'
end
and then run:
$ batali update
in the same directory. It will destroy your cookbooks
directory
by default.
IT WILL DESTROY YOUR COOKBOOKS DIRECTORY BY DEFAULT
You can make it not destroy your cookbooks directory by providing a different path. A better idea is to not use the cookbooks directory. Just ignore that sucker and let Batali do its thing.
batali resolve
- Resolve dependencies and producebatali.manifest
batali install
- Install entries from thebatali.manifest
batali update
- Performresolve
and theninstall
batali display
- Show manifest information (cookbook names, versions, etc.)
Currently supported "origins":
- RemoteSite
- Path
- Git
- ChefServer
This is simply a supermarket endpoint:
source 'https://supermarket.chef.io'
Multiple endpoints can be provided by specifying multiple
source
lines. They can also be named:
source 'https://supermarket.chef.io', :name => 'opscode'
source 'https://cookbooks.example.com', :name => 'example'
This is a Chef Server endpoint:
chef_server 'https://chef-server.example.com'
It will use the node_name
and client_key
defined within the
.chef/knife.rb
configuration by default. To use the Chef Server
URL defined within the configuration, just declare it with no
arguments:
chef_server
Paths are defined via cookbook entries:
cookbook 'example', path: '/path/to/example'
A short cut is also available when your Batali file is located at the root of the cookbook you want to add:
metadata
This will extract the name from the metadata
file and automatically
set the path '.'
.
Git sources are defined via cookbook entries:
cookbook 'example', git: 'git://git.example.com/example-repo.git', ref: 'master'
In some crazy instances, you may have a cookbook located in the subdirectory of a git repository:
cookbook 'example', git: 'git://git.example.com/example-repo.git', ref: 'master', path: 'my-cookbook'
After a batali.manifest
file has been generated, subsequent resolve
requests
will update cookbook versions using a "least impact" approach. This means that
by default if the Batali
file has not changed, running a batali resolve
will
be a noop, even if new versions of cookbooks may be available. This helps to reduce
unintended upgrades that may break things due to a required cookbook update. Allowing
a cookbook to be updated is done simply by adding it to the request:
$ batali resolve example
This will only update the version of the example cookbook, and any dependency cookbooks that must be updated to provide resolution. Dependency cookbooks that require an upgrade based on constraints will attempt to upgrade with the least impact possible by attempting to satisfy constraints within the minimum version segement possible. For example, if our Batali file contains the following:
Batali.define do
source 'https://example.com'
cookbook 'soup'
end
and after resolving we have two cookbooks in our manifest:
soup <1.0.0>
salad <0.1.4>
Some time passes and a new version of soup
is released, version 1.0.2. In that time
multiple new versions of the salad
cookbook have been released, with new features and
with some breaking changes. For this example, lets assume available versions of the salad
cookbook are:
<0.1.4>
<0.1.6>
<0.1.8>
<0.2.0>
<0.2.2>
<0.3.0>
<1.0.0>
and the soup
cookbook has updated its salad
dependency:
# soup metadata.rb
depends 'salad', '> 0.2'
Due to the behavior of existing solvers, we may expect the resolved manifest to include
salad
at the latest possible version: 1.0.0
. This is a valid solution, since the
dependency is simply stating the constraint requires salad
be greater than 0.2
and
nothing more. However, this is a very large jump from what we currently have defined
within our manifest, and jumps a major and minor version. The possibility of breaking
changes being introduced is extremely high.
Since Batali has the least impact feature enabled by default, it will only upgrade
salad
to the 0.2.2
version. This is due to the fact that the least impact feature
prefers the latest cookbook available within the closest version segement of the cookbook
version currently defined within the manifest. Since thew new soup
dependency contraint
requires versions > 0.2
, no > 0.1
versions are acceptable. Batali then looks to the
next available segment 0.2
and attempts to use the latest version: 0.2.2
. This solves the
constraint, and is used for the new solution.
Multiple cookbooks can be listed for upgrade:
$ batali resolve example ipsum lorem
or this feature can be disabled to allow everything to be updated to the latest possible versions:
$ batali resolve --no-least-impact
One of the goals for batali was being light weight resolver, in the same vein as the librarian project. This means it does nothing more than manage local cookbooks. This includes dependency and constraint resolution, as well as providing a local installation of assets defined within the generated manifest. It provides no extra features outside of that scope.
Batali does not rely on the chef gem to function. This removes any dependencies on gems that may be incompatible outside the MRI platform.
Manifest files are fully isolated. The resolver does not need to perform any actions for installing cookbooks defined within the manifest. This allows for easy transmission and direct installation of a manifest without the requirement of re-pulling information from sources.
Batali aims to solve the issue of full infrastructure resolution: resolving dependencies
from an infrastructure repository. Resolving a single dependency path will not provide
a correct resolution. This is because environments or run lists can provide extra constraints
that will result in unsolvable resolutions on individual nodes. In this case we want
to know what cookbooks are allowed within a solution, and ensure all those cookbooks
are available. Batali provides infrastructure level manifests by setting the infrastructure
flag:
$ batali resolve --infrastructure
NOTE: Depending on constraints defined within the Batali file, this can be a very large manifest
When running in infrastructure mode, Batali supports single cookbooks being loaded from
multiple sources. For example, if all the users
cookbooks greater than version 1.0
should
be available and an unreleased development version that lives in a git repository, Batali
will properly include all versions:
Batali.define do
source 'https://supermarket.chef.io'
cookbook 'users', '> 1.0'
cookbook 'users', git: 'git://example.com/org/users.git', ref: 'development'
end
The resulting Batali manifest file will include all available versions greater than 1.0
from the supermarket source and the version defined at the specified git end point.
When the infrastructure cookbooks are installed locally, the cookbook directories will have the version number as a suffix. This can cause a problem when attempting to run:
$ knife cookbook upload --all
due to knife using the directory name as the actual cookbook name. To get around this problem
the upload
command can be used directly with the correct options enabled. These options must
be defined within the config file as the options are not accessible via CLI flags. Assuming
a .chef/knife.rb
file exists:
# .chef/knife.rb
versioned_cookbooks true
$ knife upload cookbooks
Want to see what cookbooks have newer versions available within the defined constraints? Use the dry run option to see what upgrades are available without actually changing the manifest:
$ batali resolve --no-least-impact --dry-run
Tired of tracking constraints in multiple places when using Chef Environment cookbook_versions
for environment specific constraints? Let Batali manage it for you! Define your Batali
file
to enable automatic discovery:
Batali.define do
source 'https://example.com'
discover true
end
That's it! Now you can resolve for the infrastructure:
$ batali resolve --infrastructure
which will generate a resulting manifest that includes all required cookbook versions to satisfiy constraints defined by all environments.
Batali can be configured via the .batali
file. The contents of the file can be in YAML,
JSON, XML, or Ruby. Every option displayed via the help call can be set within this file.
The configuration can hold items isolated within a command's name, or defined at the top
level of the configuration file. For example:
Configuration.new do
debug true
resolve do
debug false
end
end
This configuration turns debug output on for all commands except the resolve command.
This feature is handy in situations where multiple commands may have the same flag that
should always be enabled, like the infrastructure
flag:
Configuration.new do
infrastructure true
end
When flags on the CLI contain a dash, they are referenced within the configuration file as an underscore. For example the least impact flag on the CLI looks like:
--least-impact
and the key in the configuration looks like:
least_impact
Configuration.new do
infrastructure true
resolve do
least_impact false
end
end
{
"infrastructure": true,
"resolve": {
"least_impact": false
}
}
---
:infrastructure: true
:resolve:
:least_impact: false
<configuration>
<infrastructure>true</infrastructure>
<resolve>
<least_impact>false</least_impact>
</resolve>
</configuration>
Batali can be used with Test Kitchen:
Batali can be used with ChefSpec. Add the following
line to your spec_helper.rb
file:
require 'batali/chefspec'
Batali includes a knife plugin to sync cookbooks defined within the local
batali.manifest
file with the cookbooks available from on the Chef server.
$ knife batali sync
This command will remove any cookbooks on the Chef server that are not found
within the batali.manifest
file. If cookbooks are defined within the
batali.manifest
file that have not been uploaded to the Chef server, those
cookbooks will be uploaded.
Batali can generate a static supermarket repository from a batali.manifest
. The resultant
directory can then be hosted with an httpd of choice. To generate a supermarket repository,
first run resolve
:
$ batali resolve
Next, run the supermarket
command to generate the repository:
$ batali supermarket
A new directory will be created (./supermarket
) which contains the newly generated
supermarket respository. The generated universe
file will contain URLs pointing
to localhost
, which is the default behavior. In practice, it will be desirable to
update the URL to provide the customized location:
$ batali supermarket --remote-supermarket-url="http://supermarket.example.com"
- Repository: https://github.com/spox/batali