From: Greg Blomquist <gblomqui(a)redhat.com>
Also, update DeployableXML validation to use relaxNG instead of ruby code.
Simplified form display for config server form and standardized the look and
feel with the other forms. Added launch_parameters to the deployment model.
Updating Deployable XML structure with a few changes:
- move the "default" attribute of a parameter to a child "value"
tag
- remove the "top-level" concept for configuration tooling
- remove the "tooling" element in favor of wrapping the exe, files, and
params
with a service
Update the associated wrapper code in deployable_xml
Update the associated view for displaying/collecting parameter values
Launch values collected from the user are pushed back into the deployableXML in
the deployment object before saving
---
src/app/controllers/deployments_controller.rb | 2 +
src/app/models/deployment.rb | 33 +++
src/app/util/config_server_util.rb | 10 +-
src/app/util/deployable-rng.xml | 126 +++++++++++
src/app/util/deployable_xml.rb | 224 +++++++++++++++++---
.../views/config_servers/_config_server_form.haml | 46 ++--
src/app/views/config_servers/edit.haml | 17 +--
src/app/views/config_servers/new.haml | 10 +-
src/app/views/deployments/_new.haml | 107 +++++-----
src/app/views/deployments/_parameters.haml | 12 +
src/app/views/deployments/_services.haml | 13 ++
11 files changed, 475 insertions(+), 125 deletions(-)
create mode 100644 src/app/util/deployable-rng.xml
create mode 100644 src/app/views/deployments/_parameters.haml
create mode 100644 src/app/views/deployments/_services.haml
diff --git a/src/app/controllers/deployments_controller.rb
b/src/app/controllers/deployments_controller.rb
index d727aff..65c9237 100644
--- a/src/app/controllers/deployments_controller.rb
+++ b/src/app/controllers/deployments_controller.rb
@@ -21,6 +21,7 @@ class DeploymentsController < ApplicationController
before_filter :load_deployments, :only => [:index, :show]
before_filter :load_deployment, :only => [:edit, :update]
+
viewstate :show do |default|
default.merge!({
:view => 'pretty',
@@ -329,4 +330,5 @@ class DeploymentsController < ApplicationController
return c_entry.url
end
end
+
end
diff --git a/src/app/models/deployment.rb b/src/app/models/deployment.rb
index db7923f..654a5f3 100644
--- a/src/app/models/deployment.rb
+++ b/src/app/models/deployment.rb
@@ -64,12 +64,14 @@ class Deployment < ActiveRecord::Base
validate :pool_must_be_enabled
before_destroy :destroyable?
+ before_create :inject_launch_parameters
SEARCHABLE_COLUMNS = %w(name)
USER_MUTABLE_ATTRS = ['name']
validate :validate_xml
+ validate :validate_launch_parameters
def validate_xml
begin
@@ -79,6 +81,20 @@ class Deployment < ActiveRecord::Base
end
end
+ def validate_launch_parameters
+ if not launch_parameters.empty?
+ launch_parameters.each do |asm, services|
+ services.each do |service, params|
+ params.each do |param, value|
+ if value.blank?
+ errors.add(:launch_parameters, "#{asm}.#{service}.#{param} cannot be
blank")
+ end
+ end
+ end
+ end
+ end
+ end
+
def pool_must_be_enabled
errors.add(:pool, "must be enabled") unless pool and pool.enabled?
end
@@ -147,6 +163,14 @@ class Deployment < ActiveRecord::Base
end
end
+ def launch_parameters
+ @launch_parameters ||= {}
+ end
+
+ def launch_parameters=(launch_parameters)
+ @launch_parameters = launch_parameters
+ end
+
def launch(user, config_values = nil)
if deployable_xml.requires_config_server?
return launch_in_single_account(user, config_values)
@@ -380,4 +404,13 @@ class Deployment < ActiveRecord::Base
status
end
+ def inject_launch_parameters
+ launch_parameters.each_pair do |assembly, svcs|
+ svcs.each_pair do |service, params|
+ params.each_pair do |param, value|
+ deployable_xml.set_parameter_value(assembly, service, param, value)
+ end
+ end
+ end
+ end
end
diff --git a/src/app/util/config_server_util.rb b/src/app/util/config_server_util.rb
index 728126c..e524488 100644
--- a/src/app/util/config_server_util.rb
+++ b/src/app/util/config_server_util.rb
@@ -249,12 +249,12 @@ module ConfigServerUtil
val = nil
if vals[param.name]
val = ValueParameterConfig.new(param.name, vals[param.name])
- elsif param.default
- val = ValueParameterConfig.new(param.name, param.default)
- elsif param.reference
- assembly_uuid = @assembly_uuids[param.reference.assembly]
+ elsif param.value
+ val = ValueParameterConfig.new(param.name, param.value)
+ elsif param.reference?
+ assembly_uuid = @assembly_uuids[param.reference_assembly]
val = ReferenceParameterConfig.new(param.name,
- assembly_uuid, param.reference.parameter)
+ assembly_uuid, param.reference_parameter)
else
raise InstanceConfigError, "No value provided for parameter. " +
"Assembly: #{(a)assembly.name}, Service: #{service.name}, " +
diff --git a/src/app/util/deployable-rng.xml b/src/app/util/deployable-rng.xml
new file mode 100644
index 0000000..65bb176
--- /dev/null
+++ b/src/app/util/deployable-rng.xml
@@ -0,0 +1,126 @@
+<!--
+#
+# Copyright (C) 2011 Red Hat, Inc.
+# Written by Greg Blomquist <gblomqui(a)redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA. A copy of the GNU General Public License is
+# also available at
http://www.gnu.org/copyleft/gpl.html.
+-->
+
+<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
+
+ <start>
+ <element name="deployable">
+ <attribute name="name"><text/></attribute>
+ <element name="description"><text/></element>
+ <ref name="assemblies-definition"/>
+ </element>
+ </start>
+
+ <!-- Each assembly describes an instance to launch as part of this deployable
+ -->
+ <define name="assemblies-definition">
+ <element name="assemblies">
+ <oneOrMore>
+ <element name="assembly">
+ <attribute name="name"><text/></attribute>
+ <!-- the hwp is the hardware profile that will be matched against a
+ provider -->
+ <attribute name="hwp"><text/></attribute>
+ <!-- the image id corresponds to an image warehouse image id -->
+ <element name="image">
+ <attribute name="id"><text/></attribute>
+ </element>
+ <optional>
+ <ref name="services-definition"/>
+ </optional>
+ <optional>
+ <ref name="return-values-definition"/>
+ </optional>
+ </element>
+ </oneOrMore>
+ </element>
+ </define>
+
+ <!-- services describe the configuration that should be included in the
+ launched instance -->
+ <define name="services-definition">
+ <element name="services">
+ <oneOrMore>
+ <element name="service">
+ <attribute name="name"><text/></attribute>
+ <ref name="service-configuration-definition"/>
+ </element>
+ </oneOrMore>
+ </element>
+ </define>
+
+ <!-- tooling describes the executable, additional files, and runtime
+ parameters that should be propagated to the launched instance -->
+ <define name="service-configuration-definition">
+ <element name="executable">
+ <attribute name="url"><text/></attribute>
+ </element>
+ <optional>
+ <element name="files">
+ <oneOrMore>
+ <element name="file">
+ <attribute name="url"><text/></attribute>
+ </element>
+ </oneOrMore>
+ </element>
+ </optional>
+ <optional>
+ <element name="parameters">
+ <oneOrMore>
+ <ref name="configuration-parameter-definition"/>
+ </oneOrMore>
+ </element>
+ </optional>
+ </define>
+
+ <define name="configuration-parameter-definition">
+ <element name="parameter">
+ <attribute name="name"><text/></attribute>
+ <attribute name="type">
+ <choice>
+ <!-- only supporting scalar and password types for now -->
+ <value>scalar</value>
+ <!-- password types simply indicate that the UI collecting the
+ value should obscure the input field's value -->
+ <value>password</value>
+ </choice>
+ </attribute>
+ <choice>
+ <empty/>
+ <element name="value"><text/></element>
+ <element name="reference">
+ <attribute name="assembly"><text/></attribute>
+ <attribute name="parameter"><text/></attribute>
+ </element>
+ </choice>
+ </element>
+ </define>
+
+ <define name="return-values-definition">
+ <element name="returns">
+ <oneOrMore>
+ <element name="return">
+ <attribute name="name"><text/></attribute>
+ </element>
+ </oneOrMore>
+ </element>
+ </define>
+</grammar>
diff --git a/src/app/util/deployable_xml.rb b/src/app/util/deployable_xml.rb
index f72ff12..3153c57 100644
--- a/src/app/util/deployable_xml.rb
+++ b/src/app/util/deployable_xml.rb
@@ -17,20 +17,180 @@
# MA 02110-1301, USA. A copy of the GNU General Public License is
# also available at
http://www.gnu.org/copyleft/gpl.html.
-require 'util/assembly_xml'
-
-class DeployableXML
- class ValidationError < RuntimeError; end
=begin
- <deployable name="deployable_name">
- <description>Description</description>
- <assemblies> ... (see assembly_xml.rb) </assemblies>
- </deployable>
+XML Wrapper objects for the deployable XML format
=end
- def initialize(xmlstr = "")
- @doc = Nokogiri::XML(xmlstr)
- @root = @doc.root.at_xpath('/deployable') if @doc.root
+class ValidationError < RuntimeError; end
+
+class ParameterXML
+ def initialize(node)
+ @root = node
+ end
+
+ def name
+ @root['name']
+ end
+
+ def type
+ @root['type']
+ end
+
+ def value
+ value_node.content if value_node
+ end
+
+ def value=(value)
+ end
+
+ def reference?
+ not reference_node.nil?
+ end
+
+ def reference_assembly
+ reference_node['assembly'] if reference?
+ end
+
+ def reference_parameter
+ reference_node['parameter'] if reference?
+ end
+
+ # shortcut method for accessing ref as a string
+ def reference_string
+ if reference?
+ if reference_service
+
"REF::$(#{reference_assembly}).$(#{reference_service}).$(#{reference_parameter})"
+ else
+ "REF::$(#{reference_assembly}).$(#{reference_parameter})"
+ end
+ end
+ end
+
+ private
+ def reference_node
+ @reference ||= @root.at_xpath("./reference")
+ end
+
+ def value_node
+ @value_node ||= @root.at_xpath("./value")
+ end
+
+ def value_node=(value)
+ @root.content = "<value><![CDATA[#{value}]]></value>"
+ end
+
+end
+
+class ServiceXML
+ def initialize(name, executable, files, parameters)
+ @name = name
+ @executable = executable
+ @file_nodes = files || []
+ @parameter_nodes = parameters || []
+ end
+ def name; @name; end
+
+ def executable
+ @executable['url'] if @executable
+ end
+
+ def files
+ @files ||= @file_nodes.collect do |file_node|
+ file_node['url']
+ end
+ end
+
+ def parameters
+ @parameters ||= @parameter_nodes.collect do |param_node|
+ ParameterXML.new(param_node)
+ end
+ end
+end
+
+class AssemblyXML
+
+ def initialize(xmlstr_or_node = "")
+ if xmlstr_or_node.is_a? Nokogiri::XML::Node
+ @root = xmlstr_or_node
+ else
+ doc = Nokogiri::XML(xmlstr)
+ @root = doc.root.at_xpath("/assembly") if doc.root
+ end
+ end
+
+ def validate!
+ errors = []
+ #errors << "image with uuid #{image_id} not found" unless
Image.find(image_id)
+ #if image_build
+ #errors << "build with uuid #{image_build} not found" unless
ImageBuild.find(image_build)
+ #end
+ raise ValidationError, errors.join(", ") unless errors.empty?
+ end
+
+ def image
+ @image ||= @root.at("image")
+ end
+
+ def image_id
+ @image_id ||= image['id']
+ end
+
+ def image_build
+ @image_build ||= image['build']
+ end
+
+ def services
+ # services and top-level tooling are mutually exclusive
+ @services ||= collect_services || []
+ end
+
+ def to_s
+ @root.to_s
+ end
+
+ def output_parameters
+ @output_parameters ||=
+ @root.xpath('returns/return').collect do |output_param|
+ output_param['name']
+ end
+ end
+
+ def requires_config_server?
+ not (services.empty? and output_parameters.empty?)
+ end
+
+ def to_s
+ @root.to_s
+ end
+
+ private
+ def collect_services
+ # collect the service level tooling
+ nil_if_empty((a)root.xpath("./services/service").collect do |service|
+ name = service['name']
+ exe = service.at_xpath("./executable")
+ files = service.xpath("./files/file")
+ parameters = service.xpath("./parameters/parameter")
+ ServiceXML.new(name, exe, files, parameters)
+ end)
+ end
+
+ def nil_if_empty(array)
+ array unless array.nil? or array.empty?
+ end
+end
+
+
+class DeployableXML
+
+ def initialize(xmlstr_or_node = "")
+ if xmlstr_or_node.is_a? Nokogiri::XML::Node
+ @root = xmlstr_or_node
+ elsif xmlstr_or_node.is_a? String
+ @doc = Nokogiri::XML(xmlstr_or_node)
+ @root = @doc.root.at_xpath("/deployable") if @doc.root
+ end
+ @relax_file =
"#{File.dirname(File.expand_path(__FILE__))}/deployable-rng.xml"
end
def name
@@ -43,20 +203,15 @@ class DeployableXML
end
def validate!
- raise ValidationError, "Deployable XML root element not found" unless
@doc.root
- raise ValidationError, "<deployable> element not found" unless @root
- errors = []
- errors << "deployable name not found" unless name
- unless assemblies.empty?
- assemblies.each do |assembly|
- begin
- assembly.validate!
- rescue AssemblyXML::ValidationError => e
- errors << e.message
- end
+ # load the relaxNG file and validate
+ errors = relax.validate((a)root.document) || []
+ # ...and validate the assembly
+ assemblies.each do |assembly|
+ begin
+ assembly.validate!
+ rescue ValidationError => e
+ errors << e.message
end
- else
- errors << "no assemblies found"
end
raise ValidationError, errors.join(", ") unless errors.empty?
end
@@ -64,7 +219,7 @@ class DeployableXML
def assemblies
@assemblies ||=
@root.xpath('/deployable/assemblies/assembly').collect do |assembly_node|
- AssemblyXML.new(assembly_node.to_s)
+ AssemblyXML.new(assembly_node)
end
end
@@ -72,10 +227,24 @@ class DeployableXML
@image_uuids ||=
@root.xpath('/deployable/assemblies/assembly/image').collect{|x| x['id']}
end
+ def set_parameter_value(assembly, service, parameter, value)
+ # Why not do this in the ParameterXML class?
+ # B/c we need to alter the deployable XML document and not the copy at the
+ # ParameterXML object
+ xpath =
"//assembly[@name='#{assembly}']//service[@name='#{service}']//parameter[@name='#{parameter}']"
+ param = @root.at_xpath(xpath)
+ param.inner_html = "<value><![CDATA[#{value}]]></value>"
if param
+ @assemblies = nil # reset assemblies
+ end
+
def requires_config_server?
assemblies.any? {|assembly| assembly.requires_config_server? }
end
+ def to_s
+ @root.to_s
+ end
+
def self.import_xml_from_url(url)
# Right now we allow this to raise exceptions on timeout / errors
resource = RestClient::Resource.new(url, :open_timeout => 10, :timeout => 45)
@@ -86,4 +255,9 @@ class DeployableXML
false
end
end
+
+ private
+ def relax
+ @relax ||= File.open(@relax_file) {|f| Nokogiri::XML::RelaxNG(f)}
+ end
end
diff --git a/src/app/views/config_servers/_config_server_form.haml
b/src/app/views/config_servers/_config_server_form.haml
index 5e60896..53d5c35 100644
--- a/src/app/views/config_servers/_config_server_form.haml
+++ b/src/app/views/config_servers/_config_server_form.haml
@@ -1,22 +1,24 @@
-%fieldset.clearfix
- - if not @config_server.id.nil?
- = hidden_field_tag "config_server[id]", @config_server.id
- = hidden_field_tag "provider_account_id", @provider_account.id
- = label_tag "config_server[host]", "Host: "
- = text_field_tag "config_server[host]", @config_server.host
-%fieldset.clearfix
- = label_tag "config_server[port]", "Port: "
- = text_field_tag "config_server[port]", @config_server.port
-%fieldset.clearfix
- = label_tag "config_server[username]", "Username: "
- = text_field_tag "config_server[username]", @config_server.username
-%fieldset.clearfix
- = label_tag "config_server[password]", "Password: "
- = password_field_tag "config_server[password]", @config_server.password
-%fieldset.clearfix
- = label_tag "config_server[certificate]", "Certificate: "
- = text_area_tag "config_server[certificate]", @config_server.certificate,
:rows => 20, :cols => 64
- = t('config_servers.certificate_help')
-%fieldset.clearfix
- .grid_13.alpha.omega
- = submit_tag t(:save), :class => "ra nomargin dialogbutton"
+= error_messages_for @config_server
+%fieldset
+ %p
+ = hidden_field_tag "provider_account_id", @provider_account.id
+ = form.label :host
+ = form.text_field :host, :class => 'em long'
+ %p
+ = form.label :port
+ = form.text_field :port
+ %p
+ = form.label :username
+ = form.text_field :username
+ %p
+ = form.label :password
+ = password_field_tag "config_server[password]", @config_server.password
+ %p
+ = form.label :certificate
+ %div.help
+ = t('config_servers.certificate_help')
+ = form.text_area :certificate, :rows => 20, :cols => 64
+
+%fieldset.options
+ = link_to t(:cancel), provider_account_path(@provider_account), :class =>
"button danger"
+ = submit_tag t(:save), :name => "save", :class => "button",
:id => "save_button"
diff --git a/src/app/views/config_servers/edit.haml
b/src/app/views/config_servers/edit.haml
index 42e40f8..e486db0 100644
--- a/src/app/views/config_servers/edit.haml
+++ b/src/app/views/config_servers/edit.haml
@@ -1,21 +1,10 @@
= render :partial => 'layouts/admin_header'
-%header.page-header
- %h1{:class => controller.controller_name}= "#{(a)provider_account.name} Config
Server"
- #obj_actions.button-container
- %div.button-group
- = link_to 'Cancel Editing', provider_account_path(@provider_account),
:class => 'button pill danger', :id => 'cancel_config_server_edit'
- .corner
+= render :partial => 'section_header'
%section.content-section.config_server
%header
%h2 Edit Config Server
.content
- = error_messages_for :config_server
- %h2
- = t('config_servers.edit.edit_config_server')
- - form_tag(config_server_path, { :method => :put }) do
- = render :partial => 'config_server_form', :locals => {:from =>
"edit"}
- -#%fieldset.clearfix
- .grid_13.alpha.omega
- = submit_tag t(:save), :class => "ra nomargin dialogbutton"
+ = form_for @config_server, :url => config_server_path(@config_server), :html =>
{:method => :put, :class => "generic"} do |f|
+ = render :partial => 'config_server_form', :locals => {:form =>
f}
diff --git a/src/app/views/config_servers/new.haml
b/src/app/views/config_servers/new.haml
index c21aee4..8ede90c 100644
--- a/src/app/views/config_servers/new.haml
+++ b/src/app/views/config_servers/new.haml
@@ -6,11 +6,5 @@
%h2 New Config Server
.content
- = error_messages_for :config_server
- %h2
- = t('config_servers.new.new_config_server')
- - form_for(@config_server, :url => config_servers_path) do |f|
- = render :partial => 'config_server_form', :locals => {:from =>
"new"}
- -#%fieldset
- .grid_13.alpha.omega
- = f.submit t(:add), :class => "ra nomargin dialogbutton"
+ = form_for @config_server, :url => config_servers_path, :html => {:class =>
"generic"} do |f|
+ = render :partial => 'config_server_form', :locals => {:form => f
}
diff --git a/src/app/views/deployments/_new.haml b/src/app/views/deployments/_new.haml
index 9c36365..6bf0a4b 100644
--- a/src/app/views/deployments/_new.haml
+++ b/src/app/views/deployments/_new.haml
@@ -1,68 +1,73 @@
- if @deployment.errors.any?
= render 'layouts/error_messages', :object => @deployment
-%header.page-header
- %h1{:class => controller.controller_name}
- Deployment
- = @deployment.name
- #obj_actions.button-container
- = form_for @deployment do
+= form_for @deployment do
+ %header.page-header
+ %h1{:class => controller.controller_name}
+ Deployment
+ = @deployment.name
+ #obj_actions.button-container
= hidden_field :deployment, :name
= hidden_field :deployment, :pool_id
= hidden_field :deployment, :frontend_realm_id
-# FIXME: Passing chunks of XML around in hidden form fields doesn't feel
right
= hidden_field :deployment, :deployable_xml
= submit_tag 'Launch this deployment', :class => 'button
primary', :id => 'launch_deployment'
- = link_to 'Cancel', pool_path(@pool), :class => 'button danger',
:id => 'cancel_deployment_button'
+ = link_to 'Cancel', pool_path(@pool), :class => 'button danger',
:id => 'cancel_deployment_button'
.corner
-#scoreboard
- %dl.statistics
- %ul.groups
- %li
- %dt= t("pools.index.pool")
- %dd= @pool.name
- %li
- %dt= t("deployments.deployment")
- %dd= @deployment.name
- %li
- %dt deployable definition
- %dd= link_to('view', '#', :id =>
'deployable-xml-link')
+ #scoreboard
+ %dl.statistics
+ %ul.groups
+ %li
+ %dt= t("pools.index.pool")
+ %dd= @pool.name
+ %li
+ %dt= t("deployments.deployment")
+ %dd= @deployment.name
+ %li
+ %dt deployable definition
+ %dd= link_to('view', '#', :id =>
'deployable-xml-link')
-%section.content-section.deployable_details
- %header
- %h2
- Deployable details
+ %section.content-section.deployable_details
+ %header
+ %h2
+ Deployable details
- .content
+ .content
- %ul
- %li
- Name:
- = @deployment.deployable_xml.name
- %li
- Description:
- = @deployment.deployable_xml.description
+ %ul
+ %li
+ Name:
+ = @deployment.deployable_xml.name
+ %li
+ Description:
+ = @deployment.deployable_xml.description
-%section.content-section.assemblies
- %header
- %h2
- Assemblies
+ %section.content-section.assemblies
+ %header
+ %h2
+ Assemblies
- .content
+ .content
+ -# Avoiding filter table here b/c it adds a form and we want the fields
+ -# from deployments/services to be part of the top level form, not part
+ -# of the filter form
+ %table.flat{:width => "100%"}
+ %thead
+ %th Assembly Name
+ %th Hardware Profile
+ %th Image ID
+ %th Build ID
+ - @deployment.deployable_xml.assemblies.each do |assembly|
+ %tr
+ %td= assembly.name
+ %td= assembly.hwp
+ %td= assembly.image_id
+ %td= assembly.image_build
+ -if not assembly.services.empty?
+ %tr
+ %td{:colspan => 4}
+ = render 'deployments/services', :assembly => assembly
- = filter_table([{:name => 'Assembly Name'}, {:name => 'Hardware
Profile'},
- {:name => 'Image ID'}, {:name => 'Build ID'}],
- @deployment.deployable_xml.assemblies,
- :show_select_toggle => false) do |assembly|
- %tr
- %td
- = assembly.name
- %td
- = assembly.hwp
- %td
- = assembly.image_id
- %td
- = assembly.image_build
-
- -# Quota checking to go here as well, once I understand how it works with new code
+-# Quota checking to go here as well, once I understand how it works with new code
diff --git a/src/app/views/deployments/_parameters.haml
b/src/app/views/deployments/_parameters.haml
new file mode 100644
index 0000000..d0dea80
--- /dev/null
+++ b/src/app/views/deployments/_parameters.haml
@@ -0,0 +1,12 @@
+- service.parameters.each do |parameter|
+ %tr
+ %td{:width => "30%", :style => "text-align:right"}
+ = parameter.name
+ %td{:style => "text-align:left"}
+ - if not parameter.reference?
+ - if service.name
+ = text_field_tag
"deployment[launch_parameters][#{assembly}][#{service.name}][#{parameter.name}]",
parameter.value
+ - else
+ = text_field_tag
"deployment[launch_parameters][#{assembly}][##EMPTY##][#{parameter.name}]",
parameter.value
+ - else
+ = "Reference to #{parameter.reference_string}"
diff --git a/src/app/views/deployments/_services.haml
b/src/app/views/deployments/_services.haml
new file mode 100644
index 0000000..85d6881
--- /dev/null
+++ b/src/app/views/deployments/_services.haml
@@ -0,0 +1,13 @@
+-# assumes at least one service
+%table{:width => "100%"}
+ - if assembly.services.size > 1
+ - assembly.services.each do |service|
+ %tr
+ %td
+ service.name
+ %tr
+ %td
+ %table
+ = render :partial => 'deployments/parameters', :locals =>
{:assembly => assembly.name, :service => service}
+ - else
+ = render :partial => 'deployments/parameters', :locals => {:assembly
=> assembly.name, :service => assembly.services.first}
--
1.7.4.4