In order to put something of a concrete model around the services
discussion, I've come up with an example of how we could implement this
in the context of our existing ActiveRecord model for Templates,
Assemblies, and Deployables.
What follows is entirely consistent with the description that Angus has
suggested -- so any deficiencies there are likely to be found here too,
and vice versa. In particular, note that in this model, there is no
explicit connection between Service and Template -- if the UI allows a
user to check off services that would be available to enable in
deployables which use the template, the result on the back end is simply
adding the service's associated package group to the package groups
listed for the template.
If we ultimately decide that the user will need to explicitly list the
service in the template to enable it (rather than relying on packages
listed in the template and simply warning the user and allowing
overrides), then we would simply need to add a service reference to the
template model, and then in the 'add a service to this deployable' UI
we'd filter the service list based on what's actually listed in the
templates.
Also note that, in this proposal, the services are tied to a particular
OS/version/architecture combination. This is necessary since the
associated package group will be arch/OS-specific as well. One downside
to this model is that if we want to enable the same service in multiple
places (i.e. RHEL6.1/x86_64 and Fedora15/i386), then we'll have to
create two separate unconnected Service objects with the puppet scripts
and config params copied over (the package group will be different, of
course, since it lives in a different repo). One alternate model for
this would be to make the service OS/arch-independent but supply a
separate package group for each OS/arch. If a common use case would be
to enable the same services across multiple architectures, this model
will allow us to do this without copying and pasting all the puppet
scripts and config params, as the only thing that differs will be the
package group. If we expect the config/puppet scripts to be different
(i.e. we need slightly different puppet recipes based on OS/arch), then
the currently-proposed simpler model would be better.
Anyway, here goes:
Here's an updated ActiveRecord model for adding in support for
Services. It's not complete, but it should reflect the specific object
types, attributes, and associations needed for Services.
A few inline notes are included as comments below.
# no changes here for services unless we add an explicit
# relationship to Service -- for now we only know this
# indirectly as the Service package group will be included
# in the template's package list
class Template < ActiveRecord::Base
has_many :images
has_many :instances
has_and_belongs_to_many :assemblies
end
# Updated Deployable association to indicate that the configured
# services are attached to the Deployable->Assembly relationship
class Assembly < ActiveRecord::Base
has_many :instances
has_and_belongs_to_many :templates
has_many :deployable_assemblies
has_many :deployables, :through => :deployable_assemblies
end
class Deployable < ActiveRecord::Base
has_many :deployments
has_and_belongs_to_many :deployables
has_many :deployable_assemblies
has_many :assemblies, :through => :deployable_assemblies
end
class DeployableAssembly < ActiveRecord::Base
belongs_to :assembly
belongs_to :deployable
has_many :configured_services, :dependent => :destroy
end
class CreateDeployableAssemblies < ActiveRecord::Migration
def self.up
create_table :deployable_assemblies do |t|
t.string :name # optional -- use assembly name if blank
t.integer :assembly_id
t.integer :deployable_id
end
end
end
# a Service is a collection of packages (in a package group),
# configuration scripts (puppet for now), and named configuration
# parameters used to describe a service. This is the service "in
# general", rather than connected to a specific
# deployable/template. For the moment, a service is only valid for a
# single Base OS, Version, and Architecture. If the same service needs
# to exist on more than one (say Fedora 14 x86_64 and RHEL6.1 i386),
# then two independent services must be created.
class Service < ActiveRecord::Base
has_many :service_config_scripts, :dependent => :destroy
has_many :service_params, :dependent => :destroy
has_many :configured_services, :dependent => :destroy
end
class CreateServices < ActiveRecord::Migration
def self.up
create_table :services do |t|
t.string :name
t.string :platform
t.string :platform_version
t.string :architecture
# FIXME: how are we actually referencing package group?
t.string :package_group
end
end
end
# A configuration script attached to a Service. For now we only
# support a script_type of 'puppet'.
class ServiceConfigScript < ActiveRecord::Base
belongs_to :service
validates_presence_of :name
validates_presence_of :script_type
validates_presence_of :script_content
end
class CreateServiceConfigScripts < ActiveRecord::Migration
def self.up
create_table :service_config_scripts do |t|
t.string :name
t.integer :service_id
t.string :script_type #'puppet', etc
t.text :script_content
end
end
end
# A parameter associated with a service. This doesn't hold the value
# for a particular instance, but it stores the name and type (and
# whether it's required).
class ServiceParam < ActiveRecord::Base
belongs_to :service
validates_presence_of :name
# value_type: fixed or compute-from-instance;
# for fixed 'value' in associated ServiceParamValue objects is
actual value
# for compute-from-instance value is key
end
class CreateServiceParams < ActiveRecord::Migration
def self.up
create_table :service_params do |t|
t.string :name
t.integer :service_id
t.string :value_type
t.boolean :is_required, :default => true
end
end
end
# A particular service attached to an assembly/template in the context
# of a deployable. This references a Service object and fills in the
# ServiceParams with actual values.
class ConfiguredService < ActiveRecord::Base
belongs_to :service
belongs_to :deployable_assembly
has_many :service_params
end
class CreateConfiguredServices < ActiveRecord::Migration
def self.up
create_table :configured_services do |t|
t.integer :service_id
t.integer :deployable_assembly_id
end
end
end
# Filled-in value for ServiceParam
class ServiceParamValue < ActiveRecord::Base
belongs_to :configured_service
belongs_to :service_param
validates_presence_of :value
# value_type: fixed or compute-from-instance;
# for fixed 'value' is actual value
# for compute-from-instance value is key
end
class CreateServiceParams < ActiveRecord::Migration
def self.up
create_table :service_params do |t|
t.integer :configured_service_id
t.integer :service_param_id
t.string :value
end
end
end