Module: Do::Validator

Defined in:
lib/do/validator.rb

Overview

Stateless validation over a parsed Config. Returns human-readable error strings and never touches the system.

Class Method Summary collapse

Class Method Details

.schedule_errors(task) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/do/validator.rb', line 105

def schedule_errors(task)
  sched = task.schedule
  return [] if sched.nil? || sched.empty?

  unless Task::SCHEDULES.include?(sched)
    return ["invalid schedule for task '#{task.name}'. " \
            'Expected: once, hourly, daily, weekly, or monthly.']
  end

  # Driving the calendar generator lets us reuse the exact same parsing
  # rules that will later be used to emit the systemd unit.
  begin
    Scheduler.on_calendar(task)
    []
  rescue Do::Error => e
    ["invalid schedule for task '#{task.name}': #{e.message}"]
  end
end

.valid?(config) ⇒ Boolean

Returns:

  • (Boolean)


26
27
28
# File 'lib/do/validator.rb', line 26

def valid?(config)
  validate(config).empty?
end

.validate(config) ⇒ Array<String>

Returns validation errors (empty when valid).

Parameters:

  • config (Config)

    parsed configuration

Returns:

  • (Array<String>)

    validation errors (empty when valid)



14
15
16
17
18
19
20
21
22
23
24
# File 'lib/do/validator.rb', line 14

def validate(config)
  errors = []
  errors.concat(validate_commands(config))
  errors.concat(validate_task_fields(config))
  errors.concat(validate_duplicates(config))
  errors.concat(validate_schedules(config))
  errors.concat(validate_unit_names(config))
  errors.concat(validate_directories(config))
  errors.concat(validate_environment(config))
  errors
end

.validate!(config) ⇒ Object

Raises:



31
32
33
34
35
36
# File 'lib/do/validator.rb', line 31

def validate!(config)
  errors = validate(config)
  raise ValidationError, errors unless errors.empty?

  true
end

.validate_commands(config) ⇒ Object



38
39
40
41
42
43
44
45
46
# File 'lib/do/validator.rb', line 38

def validate_commands(config)
  config.tasks.flat_map do |task|
    if task.command.nil? || task.command.to_s.strip.empty?
      ["task '#{task.name}' is missing required field 'command'"]
    else
      []
    end
  end
end

.validate_directories(config) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/do/validator.rb', line 80

def validate_directories(config)
  config.tasks.flat_map do |task|
    wd = task.working_directory
    next [] if wd.nil? || wd.empty?

    if File.directory?(File.expand_path(wd))
      []
    else
      ["working directory '#{wd}' for task '#{task.name}' does not exist"]
    end
  end
end

.validate_duplicates(config) ⇒ Object



55
56
57
58
59
60
# File 'lib/do/validator.rb', line 55

def validate_duplicates(config)
  lowered = config.tasks.map { |t| t.name.downcase }
  lowered
    .select { |n| lowered.count(n) > 1 }.uniq
    .map { |n| "duplicate task name '#{n}'" }
end

.validate_environment(config) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
# File 'lib/do/validator.rb', line 93

def validate_environment(config)
  config.tasks.flat_map do |task|
    (task.environment || {}).flat_map do |key, value|
      if key.is_a?(String) && value.is_a?(String)
        []
      else
        ["environment for task '#{task.name}' must map strings to strings"]
      end
    end
  end
end

.validate_schedules(config) ⇒ Object



62
63
64
# File 'lib/do/validator.rb', line 62

def validate_schedules(config)
  config.tasks.flat_map { |task| schedule_errors(task) }
end

.validate_task_fields(config) ⇒ Object



48
49
50
51
52
53
# File 'lib/do/validator.rb', line 48

def validate_task_fields(config)
  config.tasks.flat_map do |task|
    unknown = task.raw.keys - Config::KNOWN_TASK_FIELDS
    unknown.map { |field| "unsupported field '#{field}' for task '#{task.name}'" }
  end
end

.validate_unit_names(config) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/do/validator.rb', line 66

def validate_unit_names(config)
  config.tasks.flat_map do |task|
    name = task.name.to_s
    if name.empty?
      ['task has an empty name']
    elsif name.start_with?('.') || name =~ %r{[\s/]|\.}
      ["task name '#{name}' is not a valid systemd unit name " \
       "(must not contain spaces, '/', dots, or start with a dot)"]
    else
      []
    end
  end
end