Downgrade mysql cookbook for now
It doesn't play well with our current dev server setup
This commit is contained in:
370
cookbooks/smf/README.md
Normal file
370
cookbooks/smf/README.md
Normal file
@@ -0,0 +1,370 @@
|
||||
SMF
|
||||
===
|
||||
|
||||
## Description
|
||||
|
||||
Service Management Facility (SMF) is a tool in many Illumos and Solaris-derived operating systems
|
||||
that treats services as first class objects of the system. It provides an XML syntax for
|
||||
declaring how the system can interact with and control a service.
|
||||
|
||||
The SMF cookbook contains providers for creating or modifying a service within the SMF framework.
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
Any operating system that uses SMF, ie Solaris, SmartOS, OpenIndiana etc.
|
||||
|
||||
The `smf` provider depends on the `builder` gem, which can be installed
|
||||
via the `smf::default` recipe.
|
||||
|
||||
Requires the RBAC cookbook, which can be found at <https://supermarket.getchef.com/cookbooks/rbac>.
|
||||
|
||||
Processes can be run inside a project wrapper. In this case, look to the Resource Control cookbook,
|
||||
which can be found at <https://supermarket.getchef.com/cookbooks/resource-control>. Note that the SMF LWRP
|
||||
does not create or manage the project.
|
||||
|
||||
|
||||
## Basic Usage
|
||||
|
||||
Note that we run the `smf::default` recipe before using LWRPs from this
|
||||
cookbook.
|
||||
|
||||
```ruby
|
||||
include_recipe 'smf'
|
||||
|
||||
smf 'my-service' do
|
||||
user 'non-root-user'
|
||||
start_command 'my-service start'
|
||||
start_timeout 10
|
||||
stop_command 'pkill my-service'
|
||||
stop_timeout 5
|
||||
restart_command 'my-service restart'
|
||||
restart_timeout 60
|
||||
environment 'PATH' => '/home/non-root-user/bin',
|
||||
'RAILS_ENV' => 'staging'
|
||||
locale 'C'
|
||||
manifest_type 'application'
|
||||
service_path '/var/svc/manifest'
|
||||
notifies :restart, 'service[my-service]'
|
||||
end
|
||||
|
||||
service 'my-service' do
|
||||
action :enable
|
||||
end
|
||||
|
||||
service 'my-service' do
|
||||
action :restart
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
## Attributes
|
||||
|
||||
Ownership:
|
||||
* `user` - User to run service commands as
|
||||
* `group` - Group to run service commands as
|
||||
|
||||
RBAC
|
||||
* `authorization` - What management and value authorizations should be
|
||||
created for this service. Defaults to the service name.
|
||||
|
||||
Dependency management:
|
||||
* `include_default_dependencies` - Service should depend on file system
|
||||
and network services. Defaults to `true`. See [Dependencies](#dependencies)
|
||||
for more info.
|
||||
* `dependency` - an optional array of hashes signifying service and path
|
||||
dependencies for this service to run. See [Dependencies](#dependencies).
|
||||
|
||||
Process management:
|
||||
* `project` - Name of project to run commands in
|
||||
* `start_command`
|
||||
* `start_timeout`
|
||||
* `stop_command` - defaults to `:kill`, which basically means it will destroy every PID generated from the start command
|
||||
* `stop_timeout`
|
||||
* `restart_command` - defaults to `stop_command`, then `start_command`
|
||||
* `restart_timeout`
|
||||
* `refresh_command` - by default SMF treats this as `true`. This will be called when the SMF definition changes or
|
||||
when a `notify :reload, 'service[thing]'` is called.
|
||||
* `refresh_timeout`
|
||||
* `duration` - Can be either `contract`, `wait`, `transient` or
|
||||
`child`, but defaults to `contract`. See the [Duration](#duration) section below.
|
||||
* `environment` - Hash - Environment variables to set while running commands
|
||||
* `ignore` - Array - Faults to ignore in subprocesses. For example,
|
||||
if core dumps in children are handled by a master process and you
|
||||
don't want SMF thinking the service is exploding, you can ignore
|
||||
["core", "signal"].
|
||||
* `privileges` - Array - An array of privileges to be allowed for started processes.
|
||||
Defaults to ['basic', 'net_privaddr']
|
||||
* `property_groups` - Hash - This should be in the form `{"group name" => {"type" => "application", "key" => "value", ...}}`
|
||||
* `working_directory` - PWD that SMF should cd to in order to run commands
|
||||
* `locale` - Character encoding to use (default "C")
|
||||
|
||||
Manifest/FMRI metadata:
|
||||
* `service_path` - defaults to `/var/svc/manifest`
|
||||
* `manifest_type` - defaults to `application`
|
||||
* `stability` - String - defaults to "Evolving". Valid options are
|
||||
"Standard", "Stable", "Evolving", "Unstable", "External" and
|
||||
"Obsolete"
|
||||
|
||||
Deprecated:
|
||||
* `credentials_user` - deprecated in favor of `user`
|
||||
|
||||
|
||||
## Provider Actions
|
||||
|
||||
### :install (default)
|
||||
|
||||
This will drop a manifest XML file into `#{service_path}/#{manifest_type}/#{name}.xml`. If there is already a service
|
||||
with a name that is matched by `new_resource.name` then the FMRI of our manifest will be set to the FMRI of the
|
||||
pre-existing service. In this case, our properties will be merged into the properties of the pre-existing service.
|
||||
|
||||
In this way, updates to recipes that use the SMF provider will not delete existing service properties, but will add
|
||||
or overwrite them.
|
||||
|
||||
Because of this, the SMF provider can be used to update properties for
|
||||
services that are installed via a package manager.
|
||||
|
||||
### :delete
|
||||
|
||||
Remove an SMF definition. This stops the service if it is running.
|
||||
|
||||
### :add_rbac
|
||||
|
||||
This uses the `rbac` cookbook to define permissions that can then be applied to a user. This can be useful when local
|
||||
users should manage services that are added via packages.
|
||||
|
||||
```ruby
|
||||
smf "nginx" do
|
||||
action :add_rbac
|
||||
end
|
||||
|
||||
rbac_auth "Allow my user to manage nginx" do
|
||||
user "my_user"
|
||||
auth "nginx"
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
## Resource Notes
|
||||
|
||||
### `user`, `working_directory` and `environment`
|
||||
|
||||
SMF does a remarkably good job running services as delegated users, and removes a lot of pain if you configure a
|
||||
service correctly. There are many examples online (blogs, etc) of users wrapping their services in shell scripts with
|
||||
`start`, `stop`, `restart` arguments. In general it seems as if the intention of these scripts is to take care of the
|
||||
problem of setting environment variables and shelling out as another user.
|
||||
|
||||
The use of init scripts to wrap executables can be unnecessary with SMF, as it provides hooks for all of these use cases.
|
||||
When using `user`, SMF will assume that the `working_directory` is the user's home directory. This can be
|
||||
easily overwritten (to `/home/user/app/current` for a Rails application, for example). One thing to be careful of is
|
||||
that shell profile files will not be loaded. For this reason, if environment variables (such as PATH) are different
|
||||
on your system or require additional entries arbitrary key/values may be set using the `environment` attribute.
|
||||
|
||||
All things considered, one should think carefully about the need for an init script when working with SMF. For
|
||||
well-behaved applications with simple configuration, an init script is overkill. Applications with endless command-line
|
||||
options or that need a real login shell (for instance ruby applications that use RVM) an init script may make life
|
||||
easier.
|
||||
|
||||
### Role Based Authorization
|
||||
|
||||
By default the SMF definition creates authorizations based on the
|
||||
service name. The service user is then granted these authorizations. If
|
||||
the service is named `asplosions`, then `solaris.smf.manage.asplosions`
|
||||
and `solaris.smf.value.asplosions` will be created.
|
||||
|
||||
The authorization can be changed by manually setting `authorization` on
|
||||
the smf block:
|
||||
|
||||
```ruby
|
||||
smf 'asplosions' do
|
||||
user 'monkeyking'
|
||||
start_command 'asplode'
|
||||
authorization 'booms'
|
||||
end
|
||||
```
|
||||
|
||||
This can be helpful if there are many services configured on a single
|
||||
host, as multiple services can be collapsed into the same
|
||||
authorizations. For instance: https://illumos.org/issues/4968
|
||||
|
||||
### Dependencies
|
||||
|
||||
SMF allows services to explicitly list their dependencies on other
|
||||
services. Among other things, this ensures that services are enabled in
|
||||
the proper order on boot, so that a service doesn't fail to start
|
||||
because another service has not yet been started.
|
||||
|
||||
By default, services created by the SMF LWRP depend on the following other services:
|
||||
* svc:/milestone/sysconfig
|
||||
* svc:/system/filesystem/local
|
||||
* svc:/milestone/name-services
|
||||
* svc:/milestone/network
|
||||
|
||||
On Solaris11, `svc:/milestone/sysconfig` is replaced with
|
||||
`svc:/milestone/config`.
|
||||
|
||||
These are configured with the attribute `include_default_dependencies`,
|
||||
which defaults to `true`.
|
||||
|
||||
Other dependencies can be specified with the `dependencies` attribute,
|
||||
which takes an array of hashes as follows:
|
||||
|
||||
```ruby
|
||||
smf 'redis'
|
||||
|
||||
smf 'redis-6999' do
|
||||
start_command "..."
|
||||
dependencies [
|
||||
{name: 'redis', fmris: ['svc:/application/management/redis'],
|
||||
grouping: 'require_all', restart_on: 'restart', type: 'service'}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
Valid options for grouping:
|
||||
* require_all - All listed FMRIs must be online
|
||||
* require_any - Any of the listed FMRIs must be online
|
||||
* exclude_all - None of the listed FMRIs can be online
|
||||
* optional_all - FMRIs are either online or unable to come online
|
||||
|
||||
Valid options for restart_on:
|
||||
* error - Hardware fault
|
||||
* restart - Restarts service if the depedency is restarted
|
||||
* refresh - Restarted if the dependency is restarted or refreshed for
|
||||
any reason
|
||||
* none - Don't do anything
|
||||
|
||||
Valid options for type:
|
||||
* service - expects dependency FMRIs to be other services ie: svc:/type/of/service:instance
|
||||
* path - expects FMRIs to be paths, ie file://localhost/etc/redis/redis.conf
|
||||
|
||||
Note: the provider currently does not do any validation of these values. Also, type:path has not been extensively
|
||||
tested. Use this at your own risk, or improve the provider's compatibility with type:path and submit a pull request!
|
||||
|
||||
### Duration
|
||||
|
||||
There are several different ways that SMF can track your service. By default it uses `contract`.
|
||||
Basically, this means that it will keep track of the PIDs of all daemonized processes generated from `start_command`.
|
||||
If SMF sees that processes are cycling, it may try to restart the service. If things get too hectic, it
|
||||
may think that your service is flailing and put it into maintenance mode. If this is normal for your service,
|
||||
for instance if you have a master that occasionally reaps processes, you may want to specify additional
|
||||
configuration options.
|
||||
|
||||
If you have a job that you want managed by SMF, but which is not daemonized, another duration option is
|
||||
`transient`. In this mode, SMF will not watch any processes, but will expect that the main process exits cleanly.
|
||||
This can be used, for instance, for a script that must be run at boot time, or for a script that you want to delegate
|
||||
to particular users with Role Based Access Control. In this case, the script can be registered with SMF to run as root,
|
||||
but with the start_command delegated to your user.
|
||||
|
||||
A third option is `wait`. This covers non-daemonized processes.
|
||||
|
||||
A fourth option is `child`.
|
||||
|
||||
### Ignore
|
||||
|
||||
Sometimes you have a case where your service behaves poorly. The Ruby server Unicorn, for example, has a master
|
||||
process that likes to kill its children. This causes core dumps that SMF will interpret to be a failing service.
|
||||
Instead you can `ignore ["core", "signal"]` and SMF will stop caring about core dumps.
|
||||
|
||||
### Privileges
|
||||
|
||||
Some system calls require privileges generally only granted to superusers or particular roles. In Solaris, an
|
||||
SMF definition can also set specific privileges for contracted processes.
|
||||
|
||||
By default the SMF provider will grant 'basic' and 'net_privaddr' permissions, but this can be set as follows:
|
||||
|
||||
```ruby
|
||||
smf 'elasticsearch' do
|
||||
start_command 'elasticsearch'
|
||||
privileges ['basic', 'proc_lock_memory']
|
||||
end
|
||||
```
|
||||
|
||||
See the (privileges man page)[https://www.illumos.org/man/5/privileges] for more information.
|
||||
|
||||
### Property Groups
|
||||
|
||||
Property Groups are where you can store extra information for SMF to use later. They should be used in the
|
||||
following format:
|
||||
|
||||
```ruby
|
||||
smf "my-service" do
|
||||
start_command "do-something"
|
||||
property_groups({
|
||||
"config" => {
|
||||
"type" => "application",
|
||||
"my-property" => "property value"
|
||||
}
|
||||
})
|
||||
end
|
||||
```
|
||||
|
||||
`type` will default to `application`, and is used in the manifest XML to declare how the property group will be
|
||||
used. For this reason, `type` can not be used as a property name (ie variable).
|
||||
|
||||
One way to use property groups is to pass variables on to commands, as follows:
|
||||
|
||||
```ruby
|
||||
rails_env = node["from-chef-environment"]["rails-env"]
|
||||
|
||||
smf "unicorn" do
|
||||
start_command "bundle exec unicorn_rails -c /home/app_user/app/current/config/%{config/rails_env} -E %{config/rails_env} -D"
|
||||
start_timeout 300
|
||||
restart_command ":kill -SIGUSR2"
|
||||
restart_timeout 300
|
||||
working_directory "/home/app_user/app/current"
|
||||
property_groups({
|
||||
"config" => {
|
||||
"rails_env" => rails_env
|
||||
}
|
||||
})
|
||||
end
|
||||
```
|
||||
|
||||
This is especially handy if you have a case where your commands may come from role attributes, but can
|
||||
only work if they have access to variables set in an environment or computed in a recipe.
|
||||
|
||||
### Stability
|
||||
|
||||
This is for reference more than anything, so that administrators of a service know what to expect of possible changes to
|
||||
the service definition.
|
||||
|
||||
See: <http://www.cuddletech.com/blog/pivot/entry.php?id=182>
|
||||
|
||||
|
||||
## Working Examples
|
||||
|
||||
Please see the [examples](https://github.com/livinginthepast/smf/blob/master/EXAMPLES.md) page for
|
||||
example usages.
|
||||
|
||||
|
||||
## Cookbook upgrades, possible side effects
|
||||
|
||||
Changes to this cookbook may change the way that its internal checksums are generated for a service.
|
||||
If you `notify :restart` any service from within the `smf` block or include a `refresh_command`, please
|
||||
be aware that upgrading this cookbook may trigger a refresh or a registered notification on the first
|
||||
subsequent chef run.
|
||||
|
||||
## Contributing
|
||||
|
||||
* fork
|
||||
* file an issue to track updates/communication
|
||||
* add tests
|
||||
* rebase master into your branch
|
||||
* issue a pull request
|
||||
|
||||
Please do not increment the cookbook version in a fork. Version updates
|
||||
will be done on the master branch after any pull requests are merged.
|
||||
|
||||
When upstream changes are added to the master branch while you are
|
||||
working on a contribution, please rebase master into your branch and
|
||||
force push. A pull request should be able to be merged through a
|
||||
fast-forward, without a merge commit.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
bundle
|
||||
vagrant plugin install vagrant-smartos-zones
|
||||
bundle exec strainer test
|
||||
```
|
||||
9
cookbooks/smf/libraries/helper.rb
Normal file
9
cookbooks/smf/libraries/helper.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
unless defined?(SMFManifest::Helper)
|
||||
module SMFManifest
|
||||
# Generic helper that other helpers can inherit from.
|
||||
# Takes the current node object, as well as an optional
|
||||
# resource.
|
||||
class Helper < Struct.new(:node, :resource)
|
||||
end
|
||||
end
|
||||
end
|
||||
9
cookbooks/smf/libraries/matchers.rb
Normal file
9
cookbooks/smf/libraries/matchers.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
if defined?(ChefSpec)
|
||||
def install_smf(name)
|
||||
ChefSpec::Matchers::ResourceMatcher.new(:smf, :install, name)
|
||||
end
|
||||
|
||||
def delete_smf(name)
|
||||
ChefSpec::Matchers::ResourceMatcher.new(:smf, :delete, name)
|
||||
end
|
||||
end
|
||||
31
cookbooks/smf/libraries/rbac_helper.rb
Normal file
31
cookbooks/smf/libraries/rbac_helper.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
module SMFManifest
|
||||
# Helper methods for determining whether work needs to be done
|
||||
# with respect to assigning RBAC values to a service.
|
||||
class RBACHelper < SMFManifest::Helper
|
||||
include Chef::Mixin::ShellOut
|
||||
|
||||
def authorization_set?
|
||||
current_authorization == authorization
|
||||
end
|
||||
|
||||
def value_authorization_set?
|
||||
current_value_authorization == value_authorization
|
||||
end
|
||||
|
||||
def current_authorization
|
||||
shell_out("svcprop -p general/action_authorization #{resource.name}").stdout.chomp
|
||||
end
|
||||
|
||||
def current_value_authorization
|
||||
shell_out("svcprop -p general/value_authorization #{resource.name}").stdout.chomp
|
||||
end
|
||||
|
||||
def authorization
|
||||
"solaris.smf.manage.#{resource.authorization_name}"
|
||||
end
|
||||
|
||||
def value_authorization
|
||||
"solaris.smf.value.#{resource.authorization_name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
209
cookbooks/smf/libraries/xml_builder.rb
Normal file
209
cookbooks/smf/libraries/xml_builder.rb
Normal file
@@ -0,0 +1,209 @@
|
||||
## This is kind of a hack, to ensure that the cookbook can be
|
||||
# loaded. On first load, nokogiri may not be present. It is
|
||||
# installed at load time by recipes/default.rb, so that at run
|
||||
# time nokogiri will be present.
|
||||
#
|
||||
require 'forwardable'
|
||||
|
||||
# rubocop:disable Metrics/ClassLength
|
||||
module SMFManifest
|
||||
# XMLBuilder manages the translation of the SMF Chef resource attributes into
|
||||
# XML that can be parsed by `svccfg import`.
|
||||
#
|
||||
# SMFManifest::XMLBuilder.new(resource, node).to_xml
|
||||
#
|
||||
class XMLBuilder
|
||||
# allow delegation
|
||||
extend Forwardable
|
||||
|
||||
attr_reader :resource, :node
|
||||
|
||||
# delegate methods to :resource
|
||||
def_delegators :resource, :name, :authorization_name, :dependencies, :duration, :environment, :group, :ignore,
|
||||
:include_default_dependencies, :locale, :manifest_type, :project, :property_groups,
|
||||
:service_path, :stability, :working_directory
|
||||
|
||||
public
|
||||
|
||||
def initialize(smf_resource, node)
|
||||
@resource = smf_resource
|
||||
@node = node
|
||||
end
|
||||
|
||||
def to_xml
|
||||
@xml_output ||= xml_output
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
## methods that need to be called from within the context
|
||||
# of the Nokogiri builder block need to be protected, rather
|
||||
# than private.
|
||||
|
||||
def commands
|
||||
@commands ||= {
|
||||
'start' => resource.start_command,
|
||||
'stop' => resource.stop_command,
|
||||
'restart' => resource.restart_command,
|
||||
'refresh' => resource.refresh_command
|
||||
}
|
||||
end
|
||||
|
||||
def timeout
|
||||
@timeouts ||= {
|
||||
'start' => resource.start_timeout,
|
||||
'stop' => resource.stop_timeout,
|
||||
'restart' => resource.restart_timeout,
|
||||
'refresh' => resource.refresh_timeout
|
||||
}
|
||||
end
|
||||
|
||||
def default_dependencies
|
||||
if node.platform == 'solaris2' && node.platform_version == '5.11'
|
||||
[
|
||||
{ 'name' => 'milestone', 'value' => '/milestone/config' },
|
||||
{ 'name' => 'fs-local', 'value' => '/system/filesystem/local' },
|
||||
{ 'name' => 'name-services', 'value' => '/milestone/name-services' },
|
||||
{ 'name' => 'network', 'value' => '/milestone/network' }
|
||||
]
|
||||
else
|
||||
[
|
||||
{ 'name' => 'milestone', 'value' => '/milestone/sysconfig' },
|
||||
{ 'name' => 'fs-local', 'value' => '/system/filesystem/local' },
|
||||
{ 'name' => 'name-services', 'value' => '/milestone/name-services' },
|
||||
{ 'name' => 'network', 'value' => '/milestone/network' }
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def xml_output
|
||||
xml_builder = ::Builder::XmlMarkup.new(indent: 2)
|
||||
xml_builder.instruct!
|
||||
xml_builder.declare! :DOCTYPE, :service_bundle, :SYSTEM, '/usr/share/lib/xml/dtd/service_bundle.dtd.1'
|
||||
xml_builder.service_bundle('name' => name, 'type' => 'manifest') do |xml|
|
||||
xml.service('name' => service_fmri, 'type' => 'service', 'version' => '1') do |service|
|
||||
service.create_default_instance('enabled' => 'false')
|
||||
service.single_instance
|
||||
|
||||
if include_default_dependencies
|
||||
default_dependencies.each do |dependency|
|
||||
service.dependency('name' => dependency['name'],
|
||||
'grouping' => 'require_all',
|
||||
'restart_on' => 'none',
|
||||
'type' => 'service') do |dep|
|
||||
dep.service_fmri('value' => "svc:#{dependency['value']}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
dependencies.each do |dependency|
|
||||
service.dependency('name' => dependency['name'],
|
||||
'grouping' => dependency['grouping'],
|
||||
'restart_on' => dependency['restart_on'],
|
||||
'type' => dependency['type']) do |dep|
|
||||
dependency['fmris'].each do |service_fmri|
|
||||
dep.service_fmri('value' => service_fmri)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
service.method_context(exec_context) do |context|
|
||||
context.method_credential(credentials) if user != 'root'
|
||||
|
||||
if environment
|
||||
context.method_environment do |env|
|
||||
environment.each_pair do |var, value|
|
||||
env.envvar('name' => var, 'value' => value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
commands.each_pair do |type, command|
|
||||
if command
|
||||
service.exec_method('type' => 'method', 'name' => type, 'exec' => command, 'timeout_seconds' => timeout[type])
|
||||
end
|
||||
end
|
||||
|
||||
service.property_group('name' => 'general', 'type' => 'framework') do |group|
|
||||
group.propval('name' => 'action_authorization',
|
||||
'type' => 'astring',
|
||||
'value' => "solaris.smf.manage.#{authorization_name}")
|
||||
group.propval('name' => 'value_authorization',
|
||||
'type' => 'astring',
|
||||
'value' => "solaris.smf.value.#{authorization_name}")
|
||||
end
|
||||
|
||||
if sets_duration? || ignores_faults?
|
||||
service.property_group('name' => 'startd', 'type' => 'framework') do |group|
|
||||
group.propval('name' => 'duration', 'type' => 'astring', 'value' => duration) if sets_duration?
|
||||
group.propval('name' => 'ignore_error', 'type' => 'astring', 'value' => ignore.join(',')) if ignores_faults?
|
||||
end
|
||||
end
|
||||
|
||||
property_groups.each_pair do |name, properties|
|
||||
service.property_group('name' => name, 'type' => properties.delete('type') { |_type| 'application' }) do |group|
|
||||
properties.each_pair do |key, value|
|
||||
group.propval('name' => key, 'value' => value, 'type' => check_type(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
service.stability('value' => stability)
|
||||
|
||||
service.template do |template|
|
||||
template.common_name do |common_name|
|
||||
common_name.loctext(name, 'xml:lang' => locale)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
xml_builder.target!
|
||||
end
|
||||
|
||||
def credentials
|
||||
creds = { 'user' => user, 'privileges' => resource.privilege_list }
|
||||
creds.merge!('group' => group) unless group.nil?
|
||||
creds
|
||||
end
|
||||
|
||||
def user
|
||||
resource.user || resource.credentials_user || 'root'
|
||||
end
|
||||
|
||||
def exec_context
|
||||
context = {}
|
||||
context['working_directory'] = working_directory unless working_directory.nil?
|
||||
context['project'] = project unless project.nil?
|
||||
context
|
||||
end
|
||||
|
||||
def check_type(value)
|
||||
if value == value.to_i
|
||||
'integer'
|
||||
else
|
||||
'astring'
|
||||
end
|
||||
end
|
||||
|
||||
def ignores_faults?
|
||||
!ignore.nil?
|
||||
end
|
||||
|
||||
def sets_duration?
|
||||
duration != 'contract'
|
||||
end
|
||||
|
||||
# resource.fmri is set in the SMF :install action of the default provider.
|
||||
# If there is already a service with a name that is matched by our resource.name
|
||||
# then we grab the FMRI (fault management resource identifier) from the system.
|
||||
# If a service is not found, we set this to our own FMRI.
|
||||
def service_fmri
|
||||
resource.fmri.nil? || resource.fmri.empty? ? "#{manifest_type}/management/#{name}" : resource.fmri.gsub(/^\//, '')
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/ClassLength
|
||||
48
cookbooks/smf/metadata.json
Normal file
48
cookbooks/smf/metadata.json
Normal file
File diff suppressed because one or more lines are too long
13
cookbooks/smf/metadata.rb
Normal file
13
cookbooks/smf/metadata.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
name 'smf'
|
||||
maintainer 'Eric Saxby'
|
||||
maintainer_email 'sax@livinginthepast.org'
|
||||
license 'MIT'
|
||||
description 'A light weight resource provider (LWRP) for SMF (Service Management Facility)'
|
||||
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
|
||||
version '2.2.8'
|
||||
|
||||
supports 'smartos'
|
||||
|
||||
depends 'rbac', '>= 1.0.1'
|
||||
|
||||
suggests 'resource-control' # For managing Solaris projects, when setting project on a manifest
|
||||
143
cookbooks/smf/providers/default.rb
Normal file
143
cookbooks/smf/providers/default.rb
Normal file
@@ -0,0 +1,143 @@
|
||||
|
||||
require 'chef/mixin/shell_out'
|
||||
require 'fileutils'
|
||||
include Chef::Mixin::ShellOut
|
||||
|
||||
def load_current_resource
|
||||
find_fmri unless new_resource.fmri
|
||||
|
||||
@current_resource = Chef::Resource::Smf.new(new_resource.name)
|
||||
@current_resource.fmri(new_resource.fmri)
|
||||
@current_resource.load
|
||||
end
|
||||
|
||||
action :install do
|
||||
create_directories
|
||||
write_manifest
|
||||
create_rbac_definitions
|
||||
import_manifest
|
||||
deduplicate_manifest
|
||||
add_rbac_permissions
|
||||
|
||||
new_resource.updated_by_last_action(smf_changed?)
|
||||
new_resource.save_checksum if smf_changed?
|
||||
end
|
||||
|
||||
action :add_rbac do
|
||||
create_rbac_definitions
|
||||
service new_resource.name
|
||||
|
||||
manage = execute "add SMF authorization to allow RBAC for #{new_resource.name}" do
|
||||
command "svccfg -s #{new_resource.name} " \
|
||||
'setprop general/action_authorization=astring:' \
|
||||
"'solaris.smf.manage.#{new_resource.authorization_name}'"
|
||||
not_if { SMFManifest::RBACHelper.new(node, new_resource).authorization_set? }
|
||||
notifies :reload, "service[#{new_resource.name}]"
|
||||
end
|
||||
|
||||
value = execute "add SMF value to allow RBAC for #{new_resource.name}" do
|
||||
command "svccfg -s #{new_resource.name} " \
|
||||
'setprop general/value_authorization=astring: ' \
|
||||
'solaris.smf.value.#{new_resource.authorization_name}'
|
||||
not_if { SMFManifest::RBACHelper.new(node, new_resource).value_authorization_set? }
|
||||
notifies :reload, "service[#{new_resource.name}]"
|
||||
end
|
||||
|
||||
new_resource.updated_by_last_action(manage.updated_by_last_action? || value.updated_by_last_action?)
|
||||
end
|
||||
|
||||
action :delete do
|
||||
new_resource.updated_by_last_action(false)
|
||||
|
||||
if @current_resource.smf_exists?
|
||||
service new_resource.name do
|
||||
action [:stop, :disable]
|
||||
end
|
||||
|
||||
execute "remove service #{new_resource.name} from SMF" do
|
||||
command "svccfg delete #{new_resource.name}"
|
||||
end
|
||||
|
||||
delete_manifest
|
||||
new_resource.remove_checksum
|
||||
|
||||
new_resource.updated_by_last_action(true)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def smf_changed?
|
||||
@current_resource.checksum != new_resource.checksum || !@current_resource.smf_exists?
|
||||
end
|
||||
|
||||
def find_fmri
|
||||
fmri_check = shell_out(%(svcs -H -o FMRI #{new_resource.name}))
|
||||
if fmri_check.exitstatus == 0
|
||||
new_resource.fmri fmri_check.stdout.chomp.split(':')[1]
|
||||
else
|
||||
new_resource.fmri "/#{new_resource.manifest_type}/management/#{new_resource.name}"
|
||||
end
|
||||
end
|
||||
|
||||
def create_directories
|
||||
Chef::Log.debug "Creating manifest directory at #{new_resource.xml_path}"
|
||||
FileUtils.mkdir_p new_resource.xml_path
|
||||
end
|
||||
|
||||
def write_manifest
|
||||
return unless smf_changed?
|
||||
|
||||
Chef::Log.debug "Writing SMF manifest for #{new_resource.name}"
|
||||
::File.open(new_resource.xml_file, 'w') do |file|
|
||||
file.puts SMFManifest::XMLBuilder.new(new_resource, node).to_xml
|
||||
end
|
||||
end
|
||||
|
||||
def delete_manifest
|
||||
return unless ::File.exist?(new_resource.xml_file)
|
||||
|
||||
Chef::Log.debug "Removing SMF manifest for #{new_resource.name}"
|
||||
::File.delete(new_resource.xml_file)
|
||||
end
|
||||
|
||||
def create_rbac_definitions
|
||||
rbac new_resource.authorization_name do
|
||||
action :create
|
||||
end
|
||||
end
|
||||
|
||||
def add_rbac_permissions
|
||||
user = new_resource.user || new_resource.credentials_user || 'root'
|
||||
|
||||
rbac_auth "Add RBAC for #{new_resource.name} to #{user}" do
|
||||
user user
|
||||
auth new_resource.authorization_name
|
||||
not_if { user == 'root' }
|
||||
end
|
||||
end
|
||||
|
||||
def import_manifest
|
||||
return unless smf_changed?
|
||||
|
||||
Chef::Log.debug("importing SMF manifest #{new_resource.xml_file}")
|
||||
shell_out!("svccfg import #{new_resource.xml_file}")
|
||||
end
|
||||
|
||||
def deduplicate_manifest
|
||||
# If we are overwriting properties from an old SMF definition (from pkgsrc, etc)
|
||||
# there may be redundant XML files that we want to dereference
|
||||
name = new_resource.name
|
||||
|
||||
duplicate_manifest = shell_out("svcprop #{name} | grep -c manifestfiles").stdout.strip.to_i > 1
|
||||
return unless duplicate_manifest
|
||||
|
||||
Chef::Log.debug "Removing duplicate SMF manifest reference from #{name}"
|
||||
shell_out! "svccfg -s #{name} delprop " \
|
||||
"`svcprop #{name} | grep manifestfiles | grep -v #{new_resource.xml_file} | awk '{ print $1 }'` " \
|
||||
"&& svcadm refresh #{name}"
|
||||
end
|
||||
|
||||
def smf_defined?(fmri)
|
||||
shell_out("svcs #{fmri}").exitstatus == 0
|
||||
end
|
||||
25
cookbooks/smf/recipes/SMFServicesOK.rb
Normal file
25
cookbooks/smf/recipes/SMFServicesOK.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
directory '/opt/scripts' do
|
||||
action :create
|
||||
mode '0755'
|
||||
owner 'root'
|
||||
group 'root'
|
||||
end
|
||||
|
||||
directory '/opt/local/etc/snmp/conf.d' do
|
||||
action :create
|
||||
mode '0755'
|
||||
owner 'root'
|
||||
group 'root'
|
||||
end
|
||||
|
||||
template '/opt/scripts/SMFServicesOK.sh' do
|
||||
path '/opt/scripts/SMFServicesOK.sh'
|
||||
source 'SMFServicesOK.sh.erb'
|
||||
mode '0755'
|
||||
end
|
||||
|
||||
template 'SMFServicesOK.snmpd.conf' do
|
||||
path '/opt/local/etc/snmp/conf.d/SMFServicesOK.snmpd.conf'
|
||||
source 'SMFServicesOK.snmpd.conf.erb'
|
||||
mode '0644'
|
||||
end
|
||||
7
cookbooks/smf/recipes/default.rb
Normal file
7
cookbooks/smf/recipes/default.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
## These libraries need to be installed when the cookbook
|
||||
# is loaded, otherwise they are not available when the
|
||||
# cookbook runs.
|
||||
|
||||
chef_gem 'builder'
|
||||
|
||||
require 'builder'
|
||||
124
cookbooks/smf/resources/default.rb
Normal file
124
cookbooks/smf/resources/default.rb
Normal file
@@ -0,0 +1,124 @@
|
||||
|
||||
require 'chef/mixin/shell_out'
|
||||
include Chef::Mixin::ShellOut
|
||||
|
||||
actions :install, :add_rbac, :delete
|
||||
default_action :install
|
||||
|
||||
attribute :name, kind_of: String, name_attribute: true, required: true
|
||||
attribute :user, kind_of: [String, NilClass], default: nil
|
||||
attribute :group, kind_of: [String, NilClass], default: nil
|
||||
attribute :project, kind_of: [String, NilClass], default: nil
|
||||
|
||||
attribute :authorization, kind_of: [String, NilClass], default: nil
|
||||
|
||||
attribute :start_command, kind_of: [String, NilClass], default: nil
|
||||
attribute :start_timeout, kind_of: Integer, default: 5
|
||||
attribute :stop_command, kind_of: String, default: ':kill'
|
||||
attribute :stop_timeout, kind_of: Integer, default: 5
|
||||
attribute :restart_command, kind_of: [String, NilClass], default: nil
|
||||
attribute :restart_timeout, kind_of: Integer, default: 5
|
||||
attribute :refresh_command, kind_of: [String, NilClass], default: nil
|
||||
attribute :refresh_timeout, kind_of: Integer, default: 5
|
||||
|
||||
attribute :include_default_dependencies, kind_of: [TrueClass, FalseClass], default: true
|
||||
attribute :dependencies, kind_of: [Array], default: []
|
||||
|
||||
attribute :privileges, kind_of: [Array], default: %w(basic net_privaddr)
|
||||
attribute :working_directory, kind_of: [String, NilClass], default: nil
|
||||
attribute :environment, kind_of: [Hash, NilClass], default: nil
|
||||
attribute :locale, kind_of: String, default: 'C'
|
||||
|
||||
attribute :manifest_type, kind_of: String, default: 'application'
|
||||
attribute :service_path, kind_of: String, default: '/var/svc/manifest'
|
||||
|
||||
attribute :duration, kind_of: String, default: 'contract', regex: '(contract|wait|transient|child)'
|
||||
attribute :ignore, kind_of: [Array, NilClass], default: nil
|
||||
attribute :fmri, kind_of: String, default: nil
|
||||
|
||||
attribute :stability, kind_of: String, equal_to: %w(Standard Stable Evolving Unstable External Obsolete),
|
||||
default: 'Evolving'
|
||||
|
||||
attribute :property_groups, kind_of: Hash, default: {}
|
||||
|
||||
# Deprecated
|
||||
attribute :credentials_user, kind_of: [String, NilClass], default: nil
|
||||
|
||||
## internal methods
|
||||
|
||||
def xml_path
|
||||
"#{service_path}/#{manifest_type}"
|
||||
end
|
||||
|
||||
def xml_file
|
||||
"#{xml_path}/#{name}.xml"
|
||||
end
|
||||
|
||||
require 'fileutils'
|
||||
require 'digest/md5'
|
||||
|
||||
# Save a checksum out to a file, for future chef runs
|
||||
#
|
||||
def save_checksum
|
||||
Chef::Log.debug("Saving checksum for SMF #{name}: #{checksum}")
|
||||
::FileUtils.mkdir_p(Chef::Config.checksum_path)
|
||||
f = ::File.new(checksum_file, 'w')
|
||||
f.write checksum
|
||||
end
|
||||
|
||||
def remove_checksum
|
||||
return unless ::File.exist?(checksum_file)
|
||||
|
||||
Chef::Log.debug("Removing checksum for SMF #{name}")
|
||||
::File.delete(checksum_file)
|
||||
end
|
||||
|
||||
# Load current resource from checksum file and projects database.
|
||||
# This should only ever be called on @current_resource, never on new_resource.
|
||||
#
|
||||
def load
|
||||
@checksum ||= ::File.exist?(checksum_file) ? ::File.read(checksum_file) : ''
|
||||
@smf_exists = shell_out("svcs #{fmri}").exitstatus == 0
|
||||
Chef::Log.debug("Loaded checksum for SMF #{name}: #{@checksum}")
|
||||
Chef::Log.debug("SMF service already exists for #{fmri}? #{@smf_exists.inspect}")
|
||||
end
|
||||
|
||||
def authorization_name
|
||||
authorization || name
|
||||
end
|
||||
|
||||
def checksum
|
||||
attributes = [
|
||||
user, credentials_user, group,
|
||||
project, start_command, start_timeout, stop_command,
|
||||
stop_timeout, restart_command, restart_timeout,
|
||||
refresh_command, refresh_timeout, working_directory,
|
||||
locale, authorization, manifest_type, service_path,
|
||||
duration, ignore.to_s, include_default_dependencies,
|
||||
dependencies, fmri, stability, environment_as_string,
|
||||
privilege_list, property_groups_as_string, '0'
|
||||
]
|
||||
@checksum ||= Digest::MD5.hexdigest(attributes.join(':'))
|
||||
end
|
||||
|
||||
def checksum_file
|
||||
"#{Chef::Config.checksum_path}/smf--#{name}"
|
||||
end
|
||||
|
||||
def environment_as_string
|
||||
return nil if environment.nil?
|
||||
environment.inject('') { |memo, k, v| memo << [k, v].join('|') }
|
||||
end
|
||||
|
||||
def privilege_list
|
||||
privileges.join(',')
|
||||
end
|
||||
|
||||
def property_groups_as_string
|
||||
return nil if property_groups.empty?
|
||||
property_groups.inject('') { |memo, k, v| memo << [k, v].join('|') }
|
||||
end
|
||||
|
||||
def smf_exists?
|
||||
!!@smf_exists
|
||||
end
|
||||
13
cookbooks/smf/templates/default/SMFServicesOK.sh.erb
Normal file
13
cookbooks/smf/templates/default/SMFServicesOK.sh.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# if we're on SunOS 5.10+, we should check for beat services.
|
||||
|
||||
if [ "`uname -s`" = "SunOS" ] && [ `uname -r|cut -d. -f1` -ge 5 ] && [ `uname -r|cut -d. -f2` -ge 10 ]
|
||||
then
|
||||
B=`svcs -Ha |egrep -v "disabled|online|legacy_run"`
|
||||
if [ "foo$B" == "foo" ]
|
||||
then
|
||||
echo "OK"
|
||||
else
|
||||
echo $B
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1 @@
|
||||
extend SMFServicesOK /opt/scripts/SMFServicesOK.sh
|
||||
Reference in New Issue
Block a user