staypuft 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NTQzMDU0YjdiZTZhMzFmNWMyZWQ1OWMyZjBlZjcyNDliYTRjZThmNg==
4
+ ZDRmYjU0MzQ2ZTczOThmMmY3OGYzYTVlYTAzODRkZGVhZjg2Njk2ZQ==
5
5
  data.tar.gz: !binary |-
6
- NTlhODNjYjdjYTY1ZDdhMDIyMGRlYWE3ZDc1NjQxMTY0YTJiZWNkMg==
6
+ NzQ5NGZlMTA5MmVkNjk0NGJkNWE0MjEzYmNhODc0MjI2Mjk5OWRkYw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- N2EzOWE0ZTE0MWI4MTk4MjI3MDM2NzA1MDlhZjdhMWVkNzZmMzQzZTQyNDA3
10
- ZTMxODUzMGI4NGRlMmM3OTdjYTBiYTgxY2NhNTM1NWU4MzM5NTE2M2IyN2Uy
11
- YmYwYTkxYWYzMTI3MDFlMzhjN2FiMDVlZDNmZjZiMTgxODM2MTQ=
9
+ OGRiMmRhNGY2NDVkOWEyMzZhOWNhYTdkZmQxMjI1YjJhMzdiMDBmNGFhOWQ3
10
+ M2I5Nzg4YTJiMDIxMTdhODA2NTQ2MmRiYjI1NzMxOThjYWY5Y2I3ZjUyYmEx
11
+ OTRmYTJjZDUwZDIzZTdhMGExYWEwNjhkNjkyODYzOTY0Yjk4Yzc=
12
12
  data.tar.gz: !binary |-
13
- MmFmY2FmODIwM2VlYjgwYTQxZmVkNzk5NzNmYTJlZTU4OTFjM2EwMjgzYmNh
14
- YWVmODRkNWVmZWJiNzI2ZjBiMzMwNmFkNTdkYmE0Yjg4YzI4OWE3ZDliOTk4
15
- ZTdmMmNkN2RkYjBmZTQ1MDE3MjU4ODRhYjViODhhODM3NThiZDI=
13
+ OWRiYTVkMGI5ZjE3MzgzOGI0MDlhOGQwM2JiNTdhYjEzYTAxNDYwYWJjYmRk
14
+ OGMyNzQ0NmUxZGIzYjgzZDkxZTc3YzFjMjI5ODI2NjlmMTY5MmU3YWI1ZmFl
15
+ YTc3N2Q3OGZiZDlkYzg2M2QwZDZkNDY3ZjU4Y2M3OTBkZGQzZWI=
@@ -42,6 +42,12 @@ $(function () {
42
42
  $(this).closest('.tabbed_side_nav_table').find('.activated').removeClass('activated');
43
43
  });
44
44
 
45
+ $('#hidden_password_toggle').click(function(e) {
46
+ e.preventDefault();
47
+ $(this).children('.glyphicon-eye-open, .glyphicon-eye-close').toggleClass('hide');
48
+ $(this).siblings('.hidden_password, .shown_password').toggleClass('hide');
49
+ });
50
+
45
51
  var duration = 150;
46
52
 
47
53
  showPasswords();
@@ -52,9 +52,9 @@ span.editable, span.editable:hover {
52
52
  }
53
53
  }
54
54
 
55
- // span.edit_textfield button {
56
- // margin-left: 5px;
57
- // }
55
+ .deployment_summary_pane {
56
+ padding-top: 60px;
57
+ }
58
58
 
59
59
  .service-box {
60
60
  width: 200px;
@@ -65,7 +65,7 @@ module Staypuft
65
65
  def deploy
66
66
  deployment = Deployment.find(params[:id])
67
67
  task = ForemanTasks.async_task ::Actions::Staypuft::Deployment::Deploy, deployment
68
- redirect_to foreman_tasks_task_url(id: task)
68
+ redirect_to deployment_path(deployment)
69
69
  end
70
70
 
71
71
  # TODO remove, it's temporary
@@ -169,9 +169,6 @@ module Staypuft
169
169
  network_manager = '<%= @host.deployment.nova.network_manager %>'
170
170
  # multi_host handled inline, since it's two separate static values 'true' and 'True'
171
171
  network_overrides = '<%= @host.deployment.nova.network_overrides %>'
172
- # TODO: num_networks should be calculated based on a specified VLAN range.
173
- # Alternatively, it could be explicitly set by the user in combination
174
- # with a starting VLAN ID.
175
172
  network_num_networks = '<%= @host.deployment.nova.num_networks %>'
176
173
  network_fixed_range = '<%= @host.deployment.nova.private_fixed_range %>'
177
174
  network_floating_range = '<%= @host.deployment.nova.public_floating_range %>'
@@ -452,11 +449,11 @@ module Staypuft
452
449
  'glance_private_vip' => vip_format % :glance,
453
450
  'glance_public_vip' => vip_format % :glance,
454
451
  'heat_admin_vip' => vip_format % :heat,
455
- 'heat_cfn_admin_vip' => vip_format % :heat,
456
- 'heat_cfn_private_vip' => vip_format % :heat,
457
- 'heat_cfn_public_vip' => vip_format % :heat,
458
452
  'heat_private_vip' => vip_format % :heat,
459
453
  'heat_public_vip' => vip_format % :heat,
454
+ 'heat_cfn_admin_vip' => vip_format % :heat_cfn,
455
+ 'heat_cfn_private_vip' => vip_format % :heat_cfn,
456
+ 'heat_cfn_public_vip' => vip_format % :heat_cfn,
460
457
  'horizon_admin_vip' => vip_format % :horizon,
461
458
  'horizon_private_vip' => vip_format % :horizon,
462
459
  'horizon_public_vip' => vip_format % :horizon,
@@ -472,6 +469,7 @@ module Staypuft
472
469
  'nova_public_vip' => vip_format % :nova,
473
470
  'amqp_vip' => vip_format % :amqp,
474
471
  'swift_public_vip' => vip_format % :swift,
472
+ 'private_ip' => '<%= @host.ip %>',
475
473
  'cluster_control_ip' => '<%= @host.deployment.ips.controller_ips.first %>',
476
474
  'lb_backend_server_addrs' => '<%= @host.deployment.ips.controller_ips %>',
477
475
  'lb_backend_server_names' => '<%= @host.deployment.ips.controller_fqdns %>' },
@@ -79,13 +79,24 @@ module Staypuft
79
79
 
80
80
  def network_overrides
81
81
  { 'force_dhcp_release' => false }.tap do |h|
82
- h.update 'vlan_start' => self.vlan_range.split(':')[0] if self.vlan_manager?
82
+ h.update 'vlan_start' => vlan_start if self.vlan_manager?
83
83
  end.to_yaml
84
84
  end
85
85
 
86
- # TODO: make this dynamic
86
+ def vlan_range_arr
87
+ arr = self.vlan_range.split(':')
88
+ end
89
+
90
+ def vlan_start
91
+ vlan_range_arr[0]
92
+ end
93
+
87
94
  def num_networks
88
- 1
95
+ if self.vlan_manager?
96
+ vlan_range_arr[1].to_i - vlan_range_arr[0].to_i + 1
97
+ else
98
+ 1
99
+ end
89
100
  end
90
101
 
91
102
  class Jail < Safemode::Jail
@@ -1,11 +1,16 @@
1
1
  module Staypuft
2
2
  class Deployment::Passwords < Deployment::AbstractParamScope
3
- PASSWORD_LIST = :admin, :ceilometer_user, :cinder_db, :cinder_user,
4
- :glance_db, :glance_user, :heat_db, :heat_user, :heat_cfn_user, :mysql_root,
5
- :keystone_db, :keystone_user, :neutron_db, :neutron_user, :nova_db, :nova_user,
6
- :swift_admin, :swift_user, :amqp, :amqp_nssdb, :keystone_admin_token,
7
- :ceilometer_metering_secret, :heat_auth_encrypt_key, :horizon_secret_key,
8
- :swift_shared_secret, :neutron_metadata_proxy_secret
3
+
4
+ USER_SERVICES_PASSWORDS = :admin, :ceilometer_user, :cinder_user, :glance_user, :heat_user,
5
+ :heat_cfn_user, :keystone_user, :neutron_user, :nova_user, :swift_user, :swift_admin, :amqp
6
+
7
+ DB_SERVICES_PASSWORDS = :cinder_db, :glance_db, :heat_db, :mysql_root, :keystone_db,
8
+ :neutron_db, :nova_db, :amqp_nssdb
9
+
10
+ OTHER_PASSWORDS = :keystone_admin_token, :ceilometer_metering_secret, :heat_auth_encrypt_key,
11
+ :horizon_secret_key, :swift_shared_secret, :neutron_metadata_proxy_secret
12
+
13
+ PASSWORD_LIST = USER_SERVICES_PASSWORDS + DB_SERVICES_PASSWORDS + OTHER_PASSWORDS
9
14
 
10
15
  OTHER_ATTRS_LIST = :mode, :single_password
11
16
 
@@ -67,5 +72,20 @@ module Staypuft
67
72
  def id # compatibility with password_f
68
73
  single_password
69
74
  end
75
+
76
+ def services_passwords(filter=nil)
77
+ list = case filter
78
+ when :user
79
+ USER_SERVICES_PASSWORDS
80
+ when :db
81
+ DB_SERVICES_PASSWORDS
82
+ else
83
+ PASSWORD_LIST
84
+ end
85
+
86
+ list.inject({}) do |h,name|
87
+ h.update name => single_mode? ? single_password : self.send(name)
88
+ end
89
+ end
70
90
  end
71
91
  end
@@ -1,7 +1,7 @@
1
1
  module Staypuft
2
2
  class Deployment::VIPS < Deployment::AbstractParamScope
3
3
 
4
- VIP_NAMES = [:ceilometer, :cinder, :db, :glance, :heat, :horizon, :keystone, :loadbalancer,
4
+ VIP_NAMES = [:ceilometer, :cinder, :db, :glance, :heat, :heat_cfn, :horizon, :keystone, :loadbalancer,
5
5
  :nova, :neutron, :amqp, :swift]
6
6
  COUNT = VIP_NAMES.size
7
7
 
@@ -88,6 +88,14 @@ module Staypuft
88
88
 
89
89
  extend AttributeParamStorage
90
90
 
91
+ # Helper method for looking up a Deployment based on a foreman task
92
+ def self.find_by_foreman_task(foreman_task)
93
+ Deployment.find(ForemanTasks::Lock.where(task_id: foreman_task.id,
94
+ name: :deploy,
95
+ resource_type: 'Staypuft::Deployment')
96
+ .first.resource_id)
97
+ end
98
+
91
99
  # Returns a list of hosts that are currently being deployed.
92
100
  def in_progress_hosts(hostgroup)
93
101
  return in_progress? ? hostgroup.openstack_hosts : {}
@@ -98,6 +106,12 @@ module Staypuft
98
106
  ForemanTasks::Lock.locked? self, nil
99
107
  end
100
108
 
109
+ # Helper method for getting the in progress foreman task for this
110
+ # deployment.
111
+ def task
112
+ in_progress? ? ForemanTasks::Lock.colliding_locks(self, nil).first.task : nil
113
+ end
114
+
101
115
  # Returns all deployed hosts with no errors (default behaviour). Set
102
116
  # errors=true to return all deployed hosts that have errors
103
117
  def deployed_hosts(hostgroup, errors=false)
@@ -202,6 +216,14 @@ module Staypuft
202
216
  networking == Networking::NEUTRON
203
217
  end
204
218
 
219
+ def horizon_url
220
+ if ha?
221
+ "http://#{self.vips.get(:horizon)}"
222
+ else
223
+ self.ips.controller_ips.empty? ? nil : "http://#{self.ips.controller_ip}"
224
+ end
225
+ end
226
+
205
227
  private
206
228
 
207
229
  def update_layout
@@ -0,0 +1,12 @@
1
+ # older deface requires code prefix for erb tags
2
+ if Gem.loaded_specs['deface'].version >= Gem::Version.new('1.0.0')
3
+ erb_tag = 'erb[loud]'
4
+ else
5
+ erb_tag = 'code[erb-loud]'
6
+ end
7
+
8
+ Deface::Override.new(:virtual_path => "foreman_tasks/tasks/show",
9
+ :name => "add_return_to_deployment_button_to_foreman_tasks",
10
+ :insert_before => 'div.task-details',
11
+ :partial => "staypuft/deployments/deployment_progress_page_header"
12
+ )
@@ -0,0 +1,63 @@
1
+ <div class="modal fade" id="all_details_modal" tabindex="-1" role="dialog" aria-labelledby="All Details" aria-hidden="true">
2
+ <div class="modal-dialog">
3
+ <div class="modal-content">
4
+ <div class="modal-header">
5
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
6
+ <h2 class="modal-title" id="all_details_modal_label">
7
+ <span class="glyphicon glyphicon-cog">
8
+ </span> <%= _("All Details") %>
9
+ </h2>
10
+ </div>
11
+ <div class="modal-body" style="max-height: 600px; overflow: auto;">
12
+ <div class="row">
13
+ <div class="col-sm-8 col-sm-offset-1">
14
+ <h4><%= @deployment.ha? ? _("VIP list") : _("Controller IPs") %></h4>
15
+ <% if @deployment.ha? %>
16
+ <% Staypuft::Deployment::VIPS::VIP_NAMES.each do | vip_name | %>
17
+ <div class="row">
18
+ <label class="col-sm-4 control-label text-muted"><%= "#{vip_name}:" %></label>
19
+ <div class="col-sm-8">
20
+ <p class="form-control-static"><%= @deployment.vips.get(vip_name) %></p>
21
+ </div>
22
+ </div>
23
+ <% end %>
24
+ <% else %>
25
+ <% @deployment.ips.controller_ips.each do |ip| %>
26
+ <%= ip %>
27
+ <% end %>
28
+ <% end %>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="row">
33
+ <div class="col-sm-8 col-sm-offset-1">
34
+ <h4><%= _("User Passwords") %></h4>
35
+ <% @deployment.passwords.services_passwords(:user).each_pair do |user, pw| %>
36
+ <div class="row">
37
+ <label class="col-sm-4 control-label text-muted"><%= "#{user.to_s.gsub('_', ' ').split('user')[0].capitalize}:" %></label>
38
+ <div class="col-sm-8">
39
+ <p class="form-control-static"><%= pw %></p>
40
+ </div>
41
+ </div>
42
+ <% end %>
43
+ </div>
44
+ </div>
45
+
46
+ <div class="row">
47
+ <div class="col-sm-8 col-sm-offset-1">
48
+ <h4><%= _("Database Passwords") %></h4>
49
+ <% @deployment.passwords.services_passwords(:db).each_pair do |user, pw| %>
50
+ <div class="row">
51
+ <label class="col-sm-4 control-label text-muted"><%= "#{user.to_s.gsub('_', ' ').split('db')[0].capitalize}:" %></label>
52
+ <div class="col-sm-8">
53
+ <p class="form-control-static"><%= pw %></p>
54
+ </div>
55
+ </div>
56
+ <% end %>
57
+ </div>
58
+ </div>
59
+
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </div>
@@ -0,0 +1,11 @@
1
+ <% if @deployment = Staypuft::Deployment.find_by_foreman_task(@task) %>
2
+ <div style='width: 70%; display: inline-block'>
3
+ <h1 style='margin-top: 0'>Deployment Status</h1>
4
+ </div>
5
+
6
+ <div style='display: inline-block; float: right'>
7
+ <% if Staypuft::Deployment.find_by_foreman_task(@task) %>
8
+ <%= link_to _('Return to deployment'), deployment_path(@deployment) %>
9
+ <% end %>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,91 @@
1
+ <div class="deployment_summary_pane">
2
+ <% if @deployment.deployed? %>
3
+ <div class="row">
4
+ <div class="col-sm-12 text-center"><h1 class="glyphicon glyphicon-ok text-muted"></h1></div>
5
+ </div>
6
+ <div class="row">
7
+ <div class="col-sm-12 text-center"><h3 class="text-muted"><%= _("Deployed") %></h3></div>
8
+ </div>
9
+ <div class="row">
10
+ <div class="col-sm-8 col-sm-offset-2">
11
+ <form class="form-horizontal">
12
+ <% if @deployment.horizon_url %>
13
+ <div class="form-group">
14
+ <label class="col-sm-4 control-label text-muted"><%= _("Horizon URL") %></label>
15
+ <div class="col-sm-8">
16
+ <p class="form-control-static"><%= link_to @deployment.horizon_url, @deployment.horizon_url %></p>
17
+ </div>
18
+ </div>
19
+ <% end %>
20
+ <div class="form-group">
21
+ <label class="col-sm-4 control-label text-muted"><%= _("Username") %></label>
22
+ <div class="col-sm-8">
23
+ <p class="form-control-static">admin</p>
24
+ </div>
25
+ </div>
26
+ <div class="form-group">
27
+ <label class="col-sm-4 control-label text-muted"><%= _("Password") %></label>
28
+ <div class="col-sm-8">
29
+ <p class="form-control-static">
30
+ <span class="shown_password hide"><%= @deployment.passwords.admin %></span>
31
+ <span class="hidden_password">********</span>
32
+ <a href="" id="hidden_password_toggle">
33
+ <span class="glyphicon glyphicon-eye-open"></span>
34
+ <span class="glyphicon glyphicon-eye-close hide"></span>
35
+ </a>
36
+ </p>
37
+ </div>
38
+ </div>
39
+ </form>
40
+ <div class="row text-center">
41
+ <%= link_to(_("Access all details"), "", :'data-toggle' => "modal", :'data-target' => "#all_details_modal", :class => "btn btn-default") %>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ <% elsif @deployment.in_progress? %>
46
+ <div class="row">
47
+ <div class="col-sm-12 text-center"><h3 class="text-muted"><%= _("Deploying") %></h3></div>
48
+ </div>
49
+ <div class="row">
50
+ <div class="col-sm-6 col-sm-offset-3 text-center">
51
+ <% progress = 100 * @deployment.task.progress %>
52
+ <%= "#{progress.round(1)}% #{_('Complete')}" %>
53
+ <div class="progress progress-striped <%= 'active' if @deployment.task.state == 'running' %>">
54
+ <% classes = ['progress-bar',
55
+ case @deployment.task.result
56
+ when 'success'
57
+ 'progress-bar-success'
58
+ when 'error'
59
+ 'progress-bar-danger'
60
+ else
61
+ nil
62
+ end]
63
+ %>
64
+ <div class="<%= classes.join ' ' %>"
65
+ role="progressbar"
66
+ aria-valuenow="<%= progress %>"
67
+ aria-valuemin="0"
68
+ aria-valuemax="100"
69
+ style="width: <%= progress %>%;">
70
+ <span class="sr-only"><%= progress %>% Complete</span>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ <div class="row">
76
+ <div class="col-sm-12 text-center">
77
+ <% if @deployment.task %>
78
+ <%= display_link_if_authorized(
79
+ _('Show more details'),
80
+ hash_for_foreman_tasks_task_path(id: @deployment.task.id)) %>
81
+ <% else %>
82
+ <%= _("Deployment task has been unlocked") %>
83
+ <% end %>
84
+ </div>
85
+ </div>
86
+ <% else %>
87
+ <div class="row">
88
+ <div class="col-sm-12 text-center"><h3 class="text-muted"><%= _("Not deployed, yet.") %></h3></div>
89
+ </div>
90
+ <% end %>
91
+ </div>
@@ -60,7 +60,11 @@
60
60
  <div class="col-xs-2">
61
61
  <% if child_hostgroup.hosts.select { |h| !(!ForemanTasks::Lock.locked?(@deployment, nil) && h.open_stack_deployed?) }.count > 0 %>
62
62
  <a href="#<%= child_hostgroup.name.parameterize.underscore %>_assigned_hosts" data-toggle="tab" class="roles_list">
63
- <i class="glyphicon glyphicon-time"></i>
63
+ <% unless @deployment.in_progress? %>
64
+ <i class="glyphicon glyphicon-time"></i>
65
+ <% else %>
66
+ <%= image_tag '/assets/spinner.gif' %>
67
+ <% end %>
64
68
  <span><%= child_hostgroup.hosts.select { |h| !(!ForemanTasks::Lock.locked?(@deployment, nil) && h.open_stack_deployed?) }.count %></span>
65
69
  </a>
66
70
  <% end %>
@@ -86,6 +90,7 @@
86
90
  :hostgroup => @hostgroup,
87
91
  :child_hostgroup => child_hostgroup } %>
88
92
  <% end %>
93
+ <%= render :partial => "deployment_summary", :locals => { :deployment => @deployment } %>
89
94
  </div>
90
95
  </div>
91
96
 
@@ -101,6 +106,15 @@
101
106
  </div>
102
107
  <div class="modal-body">
103
108
  <%= (_("This action will initiate the deployment of %s ") % "<strong>#{@deployment.name}</strong>").html_safe %>
109
+ <div class="deploy-changes">
110
+ <strong><%= _("Resulting OpenStack Deployment") %>:</strong>
111
+ <% @deployment.child_hostgroups.deploy_order.each do |child_hostgroup| %>
112
+ <div <%= "class=text-muted".html_safe if child_hostgroup.hosts.count == 0 %>>
113
+ <strong><%= "#{child_hostgroup.hosts.count} x" %></strong>
114
+ <%= "#{child_hostgroup.name}" %>
115
+ </div>
116
+ <% end %>
117
+ </div>
104
118
  </div>
105
119
  <div class="modal-footer">
106
120
  <button type="button" class="btn btn-default" data-dismiss="modal"><%= _("Cancel") %></button>
@@ -113,3 +127,5 @@
113
127
  </div>
114
128
  </div>
115
129
  </div>
130
+ <%= render :partial => "deployment_access_all_details_dialogue", :locals => { :deployment => @deployment } %>
131
+
@@ -1,3 +1,3 @@
1
1
  module Staypuft
2
- VERSION = '0.1.9'
2
+ VERSION = '0.1.10'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: staypuft
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Staypuft team
@@ -78,6 +78,7 @@ files:
78
78
  - Rakefile
79
79
  - app/assets/javascripts/staypuft/staypuft.js
80
80
  - app/assets/stylesheets/staypuft/bootstrap_and_overrides.css.scss
81
+ - app/assets/stylesheets/staypuft/foreman_helper.scss
81
82
  - app/assets/stylesheets/staypuft/staypuft.css.scss
82
83
  - app/controllers/staypuft/application_controller.rb
83
84
  - app/controllers/staypuft/deployments_controller.rb
@@ -125,9 +126,13 @@ files:
125
126
  - app/models/staypuft/service.rb
126
127
  - app/models/staypuft/service/ui_params.rb
127
128
  - app/models/staypuft/service_class.rb
129
+ - app/overrides/customize_foreman_tasks_show_page.rb
128
130
  - app/overrides/hide_subscription_manager_passwords.rb
129
131
  - app/views/staypuft/deployments/_assigned_hosts_table.html.erb
130
132
  - app/views/staypuft/deployments/_deployed_hosts_table.html.erb
133
+ - app/views/staypuft/deployments/_deployment_access_all_details_dialogue.html.erb
134
+ - app/views/staypuft/deployments/_deployment_progress_page_header.html.erb
135
+ - app/views/staypuft/deployments/_deployment_summary.html.erb
131
136
  - app/views/staypuft/deployments/_free_hosts_table.html.erb
132
137
  - app/views/staypuft/deployments/_import_form.html.erb
133
138
  - app/views/staypuft/deployments/edit.html.erb
@@ -199,10 +204,9 @@ signing_key:
199
204
  specification_version: 4
200
205
  summary: OpenStack Foreman Installer
201
206
  test_files:
202
- - test/factories/staypuft_factories.rb
203
- - test/integration/navigation_test.rb
204
- - test/staypuft_test.rb
205
207
  - test/test_helper.rb
206
- - test/test_plugin_helper.rb
208
+ - test/staypuft_test.rb
207
209
  - test/unit/staypuft_test.rb
208
- has_rdoc:
210
+ - test/test_plugin_helper.rb
211
+ - test/factories/staypuft_factories.rb
212
+ - test/integration/navigation_test.rb