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%>
<%= f.submit class: 'btn btn-success' %>
<% 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' %>
<%= f.submit class: "btn btn-success" %>
<% 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:

<% end %>
<%= f.text_field :name %> <%= f.time_field :runtime, placeholder: 'HH:MM' %> + <%= (hidden_field_tag :job_id, @job_id) if @job_id%>
<%= f.submit class: 'btn btn-success' %>
<% 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