staypuft 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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