diff --git a/app/controllers/filesets_controller.rb b/app/controllers/filesets_controller.rb
index a65b7f9..08e2d7e 100644
--- a/app/controllers/filesets_controller.rb
+++ b/app/controllers/filesets_controller.rb
@@ -1,35 +1,44 @@
class FilesetsController < ApplicationController
before_action :fetch_host, only: [:new, :create]
+ before_action :fetch_job_id, only: [:new, :create]
def new
@fileset = @host.filesets.new
end
def show
end
def create
@fileset = @host.filesets.new(fetch_params)
if @fileset.save
- redirect_to host_path(@host)
+ if @job_id.present?
+ redirect_to edit_host_job_path(@host, @job_id, fileset_id: @fileset.id)
+ else
+ redirect_to new_host_job_path(@host, fileset_id: @fileset.id)
+ end
else
@fileset.include_files = nil
@fileset.exclude_directions = nil
render :new
end
end
def destroy
end
private
def fetch_host
@host = Host.find(params[:host_id])
end
+ def fetch_job_id
+ @job_id = JobTemplate.find(params[:job_id]).id if params[:job_id].present?
+ end
+
def fetch_params
params.require(:fileset).permit(:name, exclude_directions: [], include_files: [])
end
end
diff --git a/app/controllers/schedules_controller.rb b/app/controllers/schedules_controller.rb
index 94a3320..bc1682a 100644
--- a/app/controllers/schedules_controller.rb
+++ b/app/controllers/schedules_controller.rb
@@ -1,41 +1,50 @@
class SchedulesController < ApplicationController
before_action :fetch_host, only: [:new, :create]
+ before_action :fetch_job_id, only: [:new, :create]
def new
@schedule = @host.schedules.new
end
def show
end
def edit
end
def update
end
def create
@schedule = @host.schedules.new(fetch_params)
@schedule.runtime = params[:schedule][:runtime] if params[:schedule][:runtime]
if @schedule.save
- redirect_to host_path(@host)
+ if @job_id.present?
+ redirect_to edit_host_job_path(@host, @job_id, schedule_id: @schedule.id)
+ else
+ redirect_to new_host_job_path(@host, schedule_id: @schedule.id)
+ end
else
render :new
end
end
def destroy
end
private
def fetch_host
@host = Host.find(params[:host_id])
end
+ def fetch_job_id
+ @job_id = JobTemplate.find(params[:job_id]).id if params[:job_id].present?
+ end
+
def fetch_params
params.require(:schedule).permit(:name)
end
end
diff --git a/app/models/fileset.rb b/app/models/fileset.rb
index d4da768..e2cfcd1 100644
--- a/app/models/fileset.rb
+++ b/app/models/fileset.rb
@@ -1,89 +1,89 @@
class Fileset < ActiveRecord::Base
establish_connection Baas::settings[:local_db]
serialize :exclude_directions
serialize :include_directions, JSON
attr_accessor :include_files
belongs_to :host
has_many :job_templates
validates :name, presence: true, uniqueness: { scope: :host }
validate :has_included_files, on: :create
before_save :sanitize_exclude_directions, :sanitize_include_directions
DEFAULT_EXCLUDED = %w{/var/lib/bacula /proc /tmp /.journal /.fsck /bacula}
DEFAULT_INCLUDE_OPTIONS = { signature: :SHA1, compression: :GZIP }
DEFAULT_INCLUDE_FILE_LIST = ['/']
def to_bacula_config_array
['FileSet {'] +
[" Name = \"#{name_for_config}\""] +
include_directions_to_config_array +
exclude_directions_to_config_array +
['}']
end
- private
-
def name_for_config
[host.name, name].join(' ')
end
+ private
+
def has_included_files
if include_files.blank? || include_files.all?(&:blank?)
errors.add(:include_files, :cant_be_blank)
end
end
def sanitize_include_directions
files = include_files.compact.uniq.keep_if(&:present?)
return false if files.blank?
self.include_directions = { options: DEFAULT_INCLUDE_OPTIONS, file: files }
end
def sanitize_exclude_directions
self.exclude_directions = exclude_directions.keep_if(&:present?).uniq rescue nil
end
def exclude_directions_to_config_array
return [] if exclude_directions.empty?
[' Exclude {'] +
exclude_directions.map { |x| " File = \"#{x}\"" } +
[' }']
end
def include_directions_to_config_array
return [] if include_directions.blank?
[" Include {"] +
included_options +
included_files +
[' }']
end
def included_options
formatted = [" Options {"]
options = include_directions.deep_symbolize_keys[:options].
reverse_merge(DEFAULT_INCLUDE_OPTIONS)
options.each do |k,v|
if not [:wildfile].include? k
formatted << " #{k} = #{v}"
else
formatted << v.map { |f| " #{k} = \"#{f}\"" }
end
end
formatted << " }"
formatted
end
def included_files
include_directions['file'].map { |f| " File = #{f}" }
end
def included_wildfile
include_directions['wildfile'].map { |f| " wildfile = \"#{f}\"" }.join("\n")
end
end
diff --git a/app/models/job_template.rb b/app/models/job_template.rb
index 72cf628..1d0428f 100644
--- a/app/models/job_template.rb
+++ b/app/models/job_template.rb
@@ -1,83 +1,83 @@
class JobTemplate < ActiveRecord::Base
establish_connection Baas::settings[:local_db]
enum job_type: { backup: 0, restore: 1, verify: 2, admin: 3 }
belongs_to :host
belongs_to :fileset
belongs_to :schedule
validates :name, :fileset_id, presence: true
validates :schedule_id, presence: true, unless: :restore?
validates :name, uniqueness: { scope: :host }
before_save :set_job_type
scope :enabled, -> { where(enabled: true) }
# configurable
DEFAULT_OPTIONS = {
storage: :File,
pool: :Default,
messages: :Standard,
priority: 10,
:'Write Bootstrap' => '"/var/lib/bacula/%c.bsr"'
}
def to_bacula_config_array
['Job {'] +
options_array.map { |x| " #{x}" } +
DEFAULT_OPTIONS.map { |k,v| " #{k.capitalize} = #{v}" } +
['}']
end
def priority
DEFAULT_OPTIONS[:priority]
end
def enabled_human
enabled? ? 'yes' : 'no'
end
def schedule_human
schedule.present? ? schedule.name : '-'
end
def name_for_config
"#{host.name} #{name}"
end
def save_and_create_restore_job(location)
if save_status = save
restore_job = JobTemplate.new(
host: host, job_type: :restore,
fileset: fileset, name: 'Restore ' + name,
restore_location: location
)
restore_job.save
end
save_status
end
private
# Sets the default job_type as backup
def set_job_type
self.job_type = :backup if job_type.nil?
end
def options_array
result = [
"Name = \"#{name_for_config}\"",
- "FileSet = \"#{fileset.name}\"",
+ "FileSet = \"#{fileset.name_for_config}\"",
"Client = \"#{host.name}\"",
"Type = \"#{job_type.capitalize}\""
]
if restore?
result += ["Where = \"#{restore_location}\""]
else
- result += ["Schedule = \"#{schedule.name}\""]
+ result += ["Schedule = \"#{schedule.name_for_config}\""]
end
result
end
end
diff --git a/app/models/schedule.rb b/app/models/schedule.rb
index 9373621..db586d0 100644
--- a/app/models/schedule.rb
+++ b/app/models/schedule.rb
@@ -1,45 +1,45 @@
class Schedule < ActiveRecord::Base
DEFAULT_RUNS = [
'Level=Full 1st sun at ',
'Level=Differential 2nd-5th sun at ',
'Level=Incremental mon-sat at '
]
attr_accessor :runtime
serialize :runs, JSON
belongs_to :host
validates :name, :runs, presence: true
validates :name, uniqueness: { scope: :host }
before_validation :set_runs, if: Proc.new { |s| s.runtime.present? }
def to_bacula_config_array
['Schedule {'] +
[" Name = \"#{name_for_config}\""] +
runs.map {|r| " Run = #{r}" } +
['}']
end
- private
-
def name_for_config
[host.name, name].join(' ')
end
+ private
+
def set_runs
if valid_runtime?
self.runs = DEFAULT_RUNS.map { |r| r + runtime }
else
self.errors.add(:runtime, :not_valid_24h_time)
false
end
end
def valid_runtime?
runtime && runtime[/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/]
end
end
diff --git a/app/views/filesets/_form.html.erb b/app/views/filesets/_form.html.erb
index 8309239..436a60a 100644
--- a/app/views/filesets/_form.html.erb
+++ b/app/views/filesets/_form.html.erb
@@ -1,17 +1,18 @@
<%= bootstrap_form_for(@fileset, url: host_filesets_path(@host, @fileset), layout: :horizontal,
label_col: 'col-xs-3', control_col: 'col-xs-8') do |f| %>
<%= f.text_field :name %>
<%= f.text_field :include_files, label: 'Files', placeholder: '/', multiple: true %>
<%= f.text_field :include_files, label: 'Files', placeholder: '/media', multiple: true %>
<%= f.text_field :exclude_directions, label: 'Exclude', placeholder: '/proc', multiple: true %>
<%= f.text_field :exclude_directions, label: 'Exclude', placeholder: '/bacula', multiple: true %>
+ <%= (hidden_field_tag :job_id, @job_id) if @job_id%>
<% end %>
diff --git a/app/views/jobs/_form.html.erb b/app/views/jobs/_form.html.erb
index a958d31..a07e4b4 100644
--- a/app/views/jobs/_form.html.erb
+++ b/app/views/jobs/_form.html.erb
@@ -1,25 +1,27 @@
<%= bootstrap_form_for(@job, url: @job.new_record? ? host_jobs_path : host_job_path(@host, @job), layout: :horizontal, label_col: 'col-xs-4', control_col: 'col-xs-6') do |f| %>
<%= f.text_field :name %>
<%= select_with_errors_and_button(
@job, :job_template, :fileset_id, 'Fileset',
- options_from_collection_for_select(@host.filesets, :id, :name, @job.fileset_id),
- new_host_fileset_path(@host))
+ options_from_collection_for_select(@host.filesets, :id, :name,
+ params[:fileset_id] || @job.fileset_id),
+ new_host_fileset_path(@host, job_id: @job.id))
%>
<% if !@job.restore? %>
<%= select_with_errors_and_button(
@job, :job_template, :schedule_id,
- 'Schedule', options_from_collection_for_select(@host.schedules, :id, :name, @job.schedule_id),
- new_host_schedule_path(@host))
+ 'Schedule', options_from_collection_for_select(@host.schedules, :id, :name,
+ params[:schedule_id] || @job.schedule_id),
+ new_host_schedule_path(@host, job_id: @job.id))
%>
<% end %>
<%= f.text_field :restore_location, label: 'Restore Location', placeholder: '/tmp/bacula' %>
<% end %>
diff --git a/app/views/schedules/_form.html.erb b/app/views/schedules/_form.html.erb
index 9d32e92..4ebd63e 100644
--- a/app/views/schedules/_form.html.erb
+++ b/app/views/schedules/_form.html.erb
@@ -1,26 +1,27 @@
<%= bootstrap_form_for(@schedule, url: host_schedules_path(@host), layout: :horizontal,
label_col: 'col-xs-3', control_col: 'col-xs-8') do |f| %>
<% if @schedule.errors.any? %>
<%= pluralize(@schedule.errors.count, "error") %> prohibited this schedule from being saved:
<% @schedule.errors.full_messages.each do |message| %>
- <%= message %>
<% end %>
<% end %>
<%= f.text_field :name %>
<%= f.time_field :runtime, placeholder: 'HH:MM' %>
+ <%= (hidden_field_tag :job_id, @job_id) if @job_id%>
<% end %>
diff --git a/spec/controllers/filesets_controller_spec.rb b/spec/controllers/filesets_controller_spec.rb
index 18bfa32..541e0a2 100644
--- a/spec/controllers/filesets_controller_spec.rb
+++ b/spec/controllers/filesets_controller_spec.rb
@@ -1,75 +1,85 @@
require 'spec_helper'
describe FilesetsController do
let(:host) { FactoryGirl.create(:host) }
describe 'GET #new' do
before { get :new, host_id: host.id }
it 'initializes a fileset' do
expect(assigns(:fileset)).to be
end
it 'sets host the fileset\'s host' do
expect(assigns(:fileset).host).to eq(host)
end
it 'renders' do
expect(response).to render_template(:new)
end
end
describe 'POST #create' do
context 'with valid params' do
let(:params) do
{
host_id: host.id,
fileset: {
name: FactoryGirl.build(:fileset).name,
exclude_directions: ['/proc', '/tmp'],
include_files: ['/home', '/media']
}
}
end
it 'creates the fileset' do
expect { post :create, params }.
to change { host.filesets(true).count }.by(1)
end
- it 'redirects to host' do
+ it 'redirects to a new job form' do
post :create, params
- expect(response).to redirect_to(host_path(host))
+ expect(response).to redirect_to(new_host_job_path(host, fileset_id: Fileset.last.id))
+ end
+
+ context 'and an existing job' do
+ let(:job) { FactoryGirl.create(:job_template, host: host) }
+
+ it 'redirects to the job\'s edit form' do
+ post :create, params.merge(job_id: job.id)
+ expect(response).
+ to redirect_to(edit_host_job_path(host, job, fileset_id: Fileset.last.id))
+ end
end
end
context 'with invalid params' do
let(:params) do
{
host_id: host.id,
fileset: { invalid: :foo }
}
end
it 'initializes a fileset with errors' do
post :create, params
expect(assigns(:fileset)).to be
end
it 'sets the host' do
post :create, params
expect(assigns(:fileset).host).to eq(host)
end
it 'does not create the fileset' do
expect { post :create, params }.
to_not change { Fileset.count }
end
it 'renders :new' do
post :create, params
expect(response).to render_template(:new)
end
end
end
end
diff --git a/spec/controllers/schedules_controller_spec.rb b/spec/controllers/schedules_controller_spec.rb
index a6ccdb8..6bbd1a9 100644
--- a/spec/controllers/schedules_controller_spec.rb
+++ b/spec/controllers/schedules_controller_spec.rb
@@ -1,79 +1,89 @@
require 'spec_helper'
describe SchedulesController do
let(:host) { FactoryGirl.create(:host) }
describe 'GET #new' do
before { get :new, host_id: host.id }
it 'initializes a schedule' do
expect(assigns(:schedule)).to be
end
it 'sets the schedule\'s host' do
expect(assigns(:schedule).host).to eq(host)
end
it 'renders' do
expect(response).to render_template(:new)
end
end
describe 'POST #create' do
context 'with valid params' do
let(:params) do
{
host_id: host.id,
schedule: { name: FactoryGirl.build(:schedule).name, runtime: '19:17' }
}
end
it 'creates the schedule' do
expect { post :create, params }.
to change { host.schedules(true).count }.by(1)
end
- it 'redirects to host' do
+ it 'redirects to a new job form' do
post :create, params
- expect(response).to redirect_to(host_path(host))
+ expect(response).to redirect_to(new_host_job_path(host, schedule_id: Schedule.last.id))
+ end
+
+ context 'and an existing job' do
+ let(:job) { FactoryGirl.create(:job_template, host: host) }
+
+ it 'redirects to the job\'s edit form' do
+ post :create, params.merge(job_id: job.id)
+ expect(response).
+ to redirect_to(edit_host_job_path(host, job, schedule_id: Schedule.last.id))
+ end
end
end
context 'with invalid host' do
it 'raises not found error' do
expect {
post :create, { host_id: -1, schedule: { invalid: true } }
}.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'with invalid params' do
let(:params) do
{
host_id: host.id,
schedule: { invalide: :foo }
}
end
it 'initializes a schedule with errors' do
post :create, params
expect(assigns(:schedule)).to be
end
it 'does not create the schedule' do
expect { post :create, params }.
to_not change { Schedule.count }
end
it 'renders :new' do
post :create, params
expect(response).to render_template(:new)
end
it 'assigns the host to schedule' do
post :create, params
expect(assigns(:schedule).host).to eq(host)
end
end
end
end
diff --git a/spec/models/job_template_spec.rb b/spec/models/job_template_spec.rb
index 5290f1c..d879f16 100644
--- a/spec/models/job_template_spec.rb
+++ b/spec/models/job_template_spec.rb
@@ -1,118 +1,118 @@
require 'spec_helper'
describe JobTemplate do
context 'validates' do
it 'name must be present' do
expect(JobTemplate.new).to have(1).errors_on(:name)
end
it 'name must be unique on host\'s scope' do
job_1 = FactoryGirl.create(:job_template, name: 'a name')
job_2 = FactoryGirl.build(:job_template, name: 'a name')
job_3 = FactoryGirl.build(:job_template, name: 'a name', host: job_1.host)
expect(job_2).to be_valid
expect(job_3).to_not be_valid
end
it 'fileset_id must be present' do
expect(JobTemplate.new).to have(1).errors_on(:fileset_id)
end
it 'schedule_id must be present' do
expect(JobTemplate.new).to have(1).errors_on(:schedule_id)
end
it 'schedule_id must NOT be present for :restore jobs' do
expect(JobTemplate.new(job_type: :restore)).to have(0).errors_on(:schedule_id)
end
end
# automatic assignments
context 'when no job_type is given' do
let(:job_template) { FactoryGirl.create(:job_template) }
it 'sets the job_type to :backup' do
expect(job_template).to be_backup
end
end
describe '#to_bacula_config_array' do
let(:job_template) { FactoryGirl.create(:job_template) }
subject { job_template.to_bacula_config_array }
it 'has a Job structure' do
expect(subject.first).to eq('Job {')
expect(subject.last).to eq('}')
end
JobTemplate::DEFAULT_OPTIONS.each do |k, v|
it "assigns #{k.capitalize} param" do
expect(subject).to include(" #{k.capitalize} = #{v}")
end
end
it 'assigns Name param prefixed with the host\'s name' do
expect(subject).to include(" Name = \"#{job_template.name_for_config}\"")
end
it 'assigns FileSet param' do
- expect(subject).to include(" FileSet = \"#{job_template.fileset.name}\"")
+ expect(subject).to include(" FileSet = \"#{job_template.fileset.name_for_config}\"")
end
it 'assigns Client param' do
expect(subject).to include(" Client = \"#{job_template.host.name}\"")
end
it 'assigns Type param' do
expect(subject).to include(" Type = \"#{job_template.job_type.capitalize}\"")
end
it 'assigns Schedule param' do
- expect(subject).to include(" Schedule = \"#{job_template.schedule.name}\"")
+ expect(subject).to include(" Schedule = \"#{job_template.schedule.name_for_config}\"")
end
context 'for a restore job' do
let(:restore_job) { FactoryGirl.create(:job_template, :restore) }
subject { restore_job.to_bacula_config_array }
it 'does not assign a Schedule param' do
expect(subject).to_not include(" Schedule = \"#{restore_job.schedule.name}\"")
end
it 'assigns Where param' do
expect(subject).to include(" Where = \"#{restore_job.restore_location}\"")
end
end
end
describe '#save_and_create_restore_job' do
let(:host) { FactoryGirl.create(:host) }
let(:backup_job_template) do
FactoryGirl.build(:job_template, job_type: nil, host: host)
end
it 'calls save' do
backup_job_template.should_receive(:save)
backup_job_template.save_and_create_restore_job('/foo')
end
it 'creates a restore job for the same host' do
expect { backup_job_template.save_and_create_restore_job('/foo') }.
to change { host.job_templates.restore.count }.by(1)
end
it 'creates a restore job for fileset' do
backup_job_template.save_and_create_restore_job('/foo')
expect(host.job_templates.restore.pluck(:fileset_id)).
to eq([backup_job_template.fileset_id])
end
it 'sets the correct restore location' do
backup_job_template.save_and_create_restore_job('/foo')
expect(host.job_templates.restore.pluck(:restore_location)).
to eq(['/foo'])
end
end
end